add unity doc.
This commit is contained in:
374
unity/XR_RM_PICO_UDP_Sender/Assets/Scripts/PicoControllerUdpSender.cs
Executable file
374
unity/XR_RM_PICO_UDP_Sender/Assets/Scripts/PicoControllerUdpSender.cs
Executable file
@ -0,0 +1,374 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR;
|
||||
|
||||
public class PicoControllerUdpSender : MonoBehaviour
|
||||
{
|
||||
private const string HostPrefsKey = "xr_rm_udp_sender.host";
|
||||
private const string PortPrefsKey = "xr_rm_udp_sender.port";
|
||||
private const string SendHzPrefsKey = "xr_rm_udp_sender.send_hz";
|
||||
private const string ConvertPrefsKey = "xr_rm_udp_sender.convert_coordinates";
|
||||
|
||||
[Header("ROS UDP Target")]
|
||||
public string host = "192.168.9.99";
|
||||
public int port = 15000;
|
||||
|
||||
[Header("Send")]
|
||||
public float sendHz = 60.0f;
|
||||
public bool convertUnityToProjectCoordinates = true;
|
||||
public bool sendOnStart = true;
|
||||
|
||||
private UdpClient client;
|
||||
private IPEndPoint endPoint;
|
||||
private float nextSendTime;
|
||||
private bool sendEnabled;
|
||||
private readonly Packet packet = new Packet();
|
||||
private readonly List<InputDevice> deviceBuffer = new List<InputDevice>();
|
||||
|
||||
public bool SendEnabled => sendEnabled;
|
||||
public bool HasEndpoint => endPoint != null;
|
||||
public bool LeftDeviceValid { get; private set; }
|
||||
public bool RightDeviceValid { get; private set; }
|
||||
public bool LeftGripPressed { get; private set; }
|
||||
public bool RightGripPressed { get; private set; }
|
||||
public string LastError { get; private set; } = string.Empty;
|
||||
public double LastSendTime { get; private set; }
|
||||
public uint SentPackets { get; private set; }
|
||||
|
||||
[Serializable]
|
||||
private class Packet
|
||||
{
|
||||
public double t;
|
||||
public string frame_id = "xr_world";
|
||||
public Controllers controllers = new Controllers();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class Controllers
|
||||
{
|
||||
public ControllerPayload left = new ControllerPayload();
|
||||
public ControllerPayload right = new ControllerPayload();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class ControllerPayload
|
||||
{
|
||||
public bool grip;
|
||||
public float trigger;
|
||||
public float[] pos = new float[] { 0.0f, 1.0f, 0.0f };
|
||||
public float[] quat = new float[] { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
LoadConfig();
|
||||
ReopenClient();
|
||||
sendEnabled = sendOnStart;
|
||||
nextSendTime = 0.0f;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
SendSafeStopPacket();
|
||||
CloseClient();
|
||||
}
|
||||
|
||||
private void OnApplicationPause(bool pauseStatus)
|
||||
{
|
||||
if (pauseStatus)
|
||||
{
|
||||
SendSafeStopPacket();
|
||||
return;
|
||||
}
|
||||
|
||||
nextSendTime = 0.0f;
|
||||
}
|
||||
|
||||
private void OnApplicationFocus(bool hasFocus)
|
||||
{
|
||||
if (!hasFocus)
|
||||
{
|
||||
SendSafeStopPacket();
|
||||
return;
|
||||
}
|
||||
|
||||
nextSendTime = 0.0f;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!sendEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Time.unscaledTime < nextSendTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
nextSendTime = Time.unscaledTime + 1.0f / Mathf.Max(sendHz, 1.0f);
|
||||
packet.t = Time.realtimeSinceStartupAsDouble;
|
||||
|
||||
FillController(XRNode.LeftHand, packet.controllers.left);
|
||||
FillController(XRNode.RightHand, packet.controllers.right);
|
||||
SendPacket();
|
||||
}
|
||||
|
||||
public bool ApplyConfig(string newHost, int newPort, float newSendHz, bool newConvertUnityToProjectCoordinates, bool save)
|
||||
{
|
||||
string trimmedHost = newHost == null ? string.Empty : newHost.Trim();
|
||||
if (!TryParseTargetAddress(trimmedHost, out IPAddress address))
|
||||
{
|
||||
LastError = $"Invalid IPv4 address: {newHost}";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newPort < 1 || newPort > 65535)
|
||||
{
|
||||
LastError = $"Invalid UDP port: {newPort}";
|
||||
return false;
|
||||
}
|
||||
|
||||
host = trimmedHost;
|
||||
port = newPort;
|
||||
sendHz = Mathf.Clamp(newSendHz, 1.0f, 120.0f);
|
||||
convertUnityToProjectCoordinates = newConvertUnityToProjectCoordinates;
|
||||
ReopenClient(address);
|
||||
if (endPoint == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (save)
|
||||
{
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
LastError = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetSending(bool enabled)
|
||||
{
|
||||
if (!enabled && sendEnabled)
|
||||
{
|
||||
SendSafeStopPacket();
|
||||
}
|
||||
|
||||
sendEnabled = enabled;
|
||||
nextSendTime = 0.0f;
|
||||
}
|
||||
|
||||
private void LoadConfig()
|
||||
{
|
||||
string savedHost = PlayerPrefs.GetString(HostPrefsKey, host);
|
||||
if (TryParseTargetAddress(savedHost, out _))
|
||||
{
|
||||
host = savedHost.Trim();
|
||||
}
|
||||
|
||||
port = Mathf.Clamp(PlayerPrefs.GetInt(PortPrefsKey, port), 1, 65535);
|
||||
sendHz = Mathf.Clamp(PlayerPrefs.GetFloat(SendHzPrefsKey, sendHz), 1.0f, 120.0f);
|
||||
convertUnityToProjectCoordinates = PlayerPrefs.GetInt(ConvertPrefsKey, convertUnityToProjectCoordinates ? 1 : 0) != 0;
|
||||
}
|
||||
|
||||
private void SaveConfig()
|
||||
{
|
||||
PlayerPrefs.SetString(HostPrefsKey, host);
|
||||
PlayerPrefs.SetInt(PortPrefsKey, port);
|
||||
PlayerPrefs.SetFloat(SendHzPrefsKey, sendHz);
|
||||
PlayerPrefs.SetInt(ConvertPrefsKey, convertUnityToProjectCoordinates ? 1 : 0);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
private void ReopenClient()
|
||||
{
|
||||
if (TryParseTargetAddress(host, out IPAddress address))
|
||||
{
|
||||
ReopenClient(address);
|
||||
return;
|
||||
}
|
||||
|
||||
LastError = $"Invalid IPv4 address: {host}";
|
||||
CloseClient();
|
||||
endPoint = null;
|
||||
}
|
||||
|
||||
private void ReopenClient(IPAddress address)
|
||||
{
|
||||
try
|
||||
{
|
||||
CloseClient();
|
||||
client = new UdpClient();
|
||||
endPoint = new IPEndPoint(address, port);
|
||||
LastError = string.Empty;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
LastError = exception.Message;
|
||||
CloseClient();
|
||||
endPoint = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void FillController(XRNode node, ControllerPayload payload)
|
||||
{
|
||||
InputDevice device = GetControllerDevice(node);
|
||||
bool isLeft = node == XRNode.LeftHand;
|
||||
if (isLeft)
|
||||
{
|
||||
LeftDeviceValid = device.isValid;
|
||||
}
|
||||
else
|
||||
{
|
||||
RightDeviceValid = device.isValid;
|
||||
}
|
||||
|
||||
if (!device.isValid)
|
||||
{
|
||||
payload.grip = false;
|
||||
payload.trigger = 0.0f;
|
||||
SetGripState(isLeft, false);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 position = Vector3.zero;
|
||||
Quaternion rotation = Quaternion.identity;
|
||||
float trigger = 0.0f;
|
||||
float gripValue = 0.0f;
|
||||
bool gripButton = false;
|
||||
|
||||
device.TryGetFeatureValue(CommonUsages.devicePosition, out position);
|
||||
device.TryGetFeatureValue(CommonUsages.deviceRotation, out rotation);
|
||||
device.TryGetFeatureValue(CommonUsages.trigger, out trigger);
|
||||
device.TryGetFeatureValue(CommonUsages.grip, out gripValue);
|
||||
device.TryGetFeatureValue(CommonUsages.gripButton, out gripButton);
|
||||
|
||||
payload.grip = gripButton || gripValue > 0.5f;
|
||||
SetGripState(isLeft, payload.grip);
|
||||
payload.trigger = Mathf.Clamp01(trigger);
|
||||
|
||||
if (convertUnityToProjectCoordinates)
|
||||
{
|
||||
payload.pos[0] = position.x;
|
||||
payload.pos[1] = position.y;
|
||||
payload.pos[2] = -position.z;
|
||||
|
||||
payload.quat[0] = -rotation.x;
|
||||
payload.quat[1] = -rotation.y;
|
||||
payload.quat[2] = rotation.z;
|
||||
payload.quat[3] = rotation.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
payload.pos[0] = position.x;
|
||||
payload.pos[1] = position.y;
|
||||
payload.pos[2] = position.z;
|
||||
|
||||
payload.quat[0] = rotation.x;
|
||||
payload.quat[1] = rotation.y;
|
||||
payload.quat[2] = rotation.z;
|
||||
payload.quat[3] = rotation.w;
|
||||
}
|
||||
}
|
||||
|
||||
private InputDevice GetControllerDevice(XRNode node)
|
||||
{
|
||||
InputDevice device = InputDevices.GetDeviceAtXRNode(node);
|
||||
if (device.isValid)
|
||||
{
|
||||
return device;
|
||||
}
|
||||
|
||||
InputDeviceCharacteristics handedness = node == XRNode.LeftHand
|
||||
? InputDeviceCharacteristics.Left
|
||||
: InputDeviceCharacteristics.Right;
|
||||
|
||||
device = FindControllerByCharacteristics(
|
||||
InputDeviceCharacteristics.Controller | InputDeviceCharacteristics.HeldInHand | handedness);
|
||||
if (device.isValid)
|
||||
{
|
||||
return device;
|
||||
}
|
||||
|
||||
return FindControllerByCharacteristics(InputDeviceCharacteristics.Controller | handedness);
|
||||
}
|
||||
|
||||
private InputDevice FindControllerByCharacteristics(InputDeviceCharacteristics characteristics)
|
||||
{
|
||||
deviceBuffer.Clear();
|
||||
InputDevices.GetDevicesWithCharacteristics(characteristics, deviceBuffer);
|
||||
for (int i = 0; i < deviceBuffer.Count; i++)
|
||||
{
|
||||
if (deviceBuffer[i].isValid)
|
||||
{
|
||||
return deviceBuffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private static bool TryParseTargetAddress(string value, out IPAddress address)
|
||||
{
|
||||
string trimmed = value == null ? string.Empty : value.Trim();
|
||||
return IPAddress.TryParse(trimmed, out address) && address.AddressFamily == AddressFamily.InterNetwork;
|
||||
}
|
||||
|
||||
private void SetGripState(bool isLeft, bool pressed)
|
||||
{
|
||||
if (isLeft)
|
||||
{
|
||||
LeftGripPressed = pressed;
|
||||
}
|
||||
else
|
||||
{
|
||||
RightGripPressed = pressed;
|
||||
}
|
||||
}
|
||||
|
||||
private void SendSafeStopPacket()
|
||||
{
|
||||
packet.t = Time.realtimeSinceStartupAsDouble;
|
||||
packet.controllers.left.grip = false;
|
||||
packet.controllers.left.trigger = 0.0f;
|
||||
packet.controllers.right.grip = false;
|
||||
packet.controllers.right.trigger = 0.0f;
|
||||
LeftGripPressed = false;
|
||||
RightGripPressed = false;
|
||||
SendPacket();
|
||||
}
|
||||
|
||||
private void SendPacket()
|
||||
{
|
||||
if (client == null || endPoint == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string json = JsonUtility.ToJson(packet);
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(json);
|
||||
try
|
||||
{
|
||||
client.Send(bytes, bytes.Length, endPoint);
|
||||
LastSendTime = Time.realtimeSinceStartupAsDouble;
|
||||
SentPackets++;
|
||||
LastError = string.Empty;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
LastError = exception.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseClient()
|
||||
{
|
||||
client?.Close();
|
||||
client = null;
|
||||
}
|
||||
}
|
||||
11
unity/XR_RM_PICO_UDP_Sender/Assets/Scripts/PicoControllerUdpSender.cs.meta
Executable file
11
unity/XR_RM_PICO_UDP_Sender/Assets/Scripts/PicoControllerUdpSender.cs.meta
Executable file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4c7a11d85854eddb40a6a614bfb129c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
372
unity/XR_RM_PICO_UDP_Sender/Assets/Scripts/PicoKeepAwake.cs
Normal file
372
unity/XR_RM_PICO_UDP_Sender/Assets/Scripts/PicoKeepAwake.cs
Normal file
@ -0,0 +1,372 @@
|
||||
using System;
|
||||
using Unity.XR.PICO.TOBSupport;
|
||||
using UnityEngine;
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public class PicoKeepAwake : MonoBehaviour
|
||||
{
|
||||
[Header("Policy")]
|
||||
public bool keepAwake = true;
|
||||
public bool applyWhenSendingOnly = true;
|
||||
|
||||
[Header("Backends")]
|
||||
public bool usePicoEnterpriseApi = true;
|
||||
public bool useAndroidWindowFlag = true;
|
||||
public float picoRefreshSeconds = 30.0f;
|
||||
|
||||
private const int AndroidFlagKeepScreenOn = 128;
|
||||
|
||||
private PicoControllerUdpSender sender;
|
||||
private bool enterpriseInitialized;
|
||||
private bool enterpriseBound;
|
||||
private bool enterpriseBindingRequested;
|
||||
private bool picoSettingsApplied;
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
private bool picoSystemDelaysCaptured;
|
||||
private SleepDelayTimeEnum previousSleepDelay;
|
||||
private ScreenOffDelayTimeEnum previousScreenOffDelay;
|
||||
#endif
|
||||
private float nextPicoRefreshTime;
|
||||
private int lastScreenOffDelayResult = int.MinValue;
|
||||
private string lastPicoError = string.Empty;
|
||||
|
||||
public bool KeepAwakeActive { get; private set; }
|
||||
public bool EnterpriseBound => enterpriseBound;
|
||||
public string StatusText { get; private set; } = "KeepAwake idle";
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
sender = GetComponent<PicoControllerUdpSender>();
|
||||
ApplyUnityKeepAwakeDefaults();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (usePicoEnterpriseApi)
|
||||
{
|
||||
EnsureEnterpriseBinding();
|
||||
}
|
||||
|
||||
RefreshDesiredState(true);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
RefreshDesiredState(false);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
DisableKeepAwake();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
DisableKeepAwake();
|
||||
}
|
||||
|
||||
private void OnApplicationPause(bool pauseStatus)
|
||||
{
|
||||
if (!pauseStatus)
|
||||
{
|
||||
nextPicoRefreshTime = 0.0f;
|
||||
RefreshDesiredState(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationFocus(bool hasFocus)
|
||||
{
|
||||
if (hasFocus)
|
||||
{
|
||||
nextPicoRefreshTime = 0.0f;
|
||||
RefreshDesiredState(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshDesiredState(bool force)
|
||||
{
|
||||
bool shouldKeepAwake = keepAwake && (!applyWhenSendingOnly || sender == null || sender.SendEnabled);
|
||||
if (!shouldKeepAwake)
|
||||
{
|
||||
if (KeepAwakeActive)
|
||||
{
|
||||
DisableKeepAwake();
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshStatus();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!KeepAwakeActive)
|
||||
{
|
||||
EnableKeepAwake();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((force || Time.unscaledTime >= nextPicoRefreshTime) && usePicoEnterpriseApi)
|
||||
{
|
||||
ApplyPicoEnterpriseKeepAwake();
|
||||
}
|
||||
|
||||
RefreshStatus();
|
||||
}
|
||||
|
||||
private void EnableKeepAwake()
|
||||
{
|
||||
ApplyUnityKeepAwakeDefaults();
|
||||
SetAndroidKeepScreenOn(true);
|
||||
|
||||
KeepAwakeActive = true;
|
||||
nextPicoRefreshTime = 0.0f;
|
||||
|
||||
if (usePicoEnterpriseApi)
|
||||
{
|
||||
EnsureEnterpriseBinding();
|
||||
ApplyPicoEnterpriseKeepAwake();
|
||||
}
|
||||
|
||||
RefreshStatus();
|
||||
}
|
||||
|
||||
private void DisableKeepAwake()
|
||||
{
|
||||
if (!KeepAwakeActive)
|
||||
{
|
||||
RefreshStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (usePicoEnterpriseApi)
|
||||
{
|
||||
ReleasePicoEnterpriseWakeLock();
|
||||
}
|
||||
|
||||
SetAndroidKeepScreenOn(false);
|
||||
Screen.sleepTimeout = SleepTimeout.SystemSetting;
|
||||
KeepAwakeActive = false;
|
||||
RefreshStatus();
|
||||
}
|
||||
|
||||
private static void ApplyUnityKeepAwakeDefaults()
|
||||
{
|
||||
Application.runInBackground = true;
|
||||
Screen.sleepTimeout = SleepTimeout.NeverSleep;
|
||||
}
|
||||
|
||||
private void EnsureEnterpriseBinding()
|
||||
{
|
||||
if (!usePicoEnterpriseApi || enterpriseBindingRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
enterpriseBindingRequested = true;
|
||||
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
try
|
||||
{
|
||||
enterpriseInitialized = PXR_Enterprise.InitEnterpriseService(false);
|
||||
PXR_Enterprise.BindEnterpriseService(OnEnterpriseBound);
|
||||
lastPicoError = enterpriseInitialized ? string.Empty : "PICO enterprise init failed";
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
enterpriseInitialized = false;
|
||||
lastPicoError = exception.Message;
|
||||
}
|
||||
#else
|
||||
enterpriseInitialized = true;
|
||||
enterpriseBound = true;
|
||||
#endif
|
||||
RefreshStatus();
|
||||
}
|
||||
|
||||
private void OnEnterpriseBound(bool success)
|
||||
{
|
||||
enterpriseBound = success;
|
||||
if (!success)
|
||||
{
|
||||
lastPicoError = "PICO enterprise bind failed";
|
||||
RefreshStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
lastPicoError = string.Empty;
|
||||
nextPicoRefreshTime = 0.0f;
|
||||
if (KeepAwakeActive)
|
||||
{
|
||||
ApplyPicoEnterpriseKeepAwake();
|
||||
}
|
||||
|
||||
RefreshStatus();
|
||||
}
|
||||
|
||||
private void ApplyPicoEnterpriseKeepAwake()
|
||||
{
|
||||
nextPicoRefreshTime = Time.unscaledTime + Mathf.Max(5.0f, picoRefreshSeconds);
|
||||
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
if (!enterpriseInitialized)
|
||||
{
|
||||
EnsureEnterpriseBinding();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CapturePicoSystemDelays();
|
||||
PXR_Enterprise.AcquireWakeLock();
|
||||
PXR_Enterprise.PropertySetSleepDelay(SleepDelayTimeEnum.NEVER);
|
||||
PXR_Enterprise.PropertySetScreenOffDelay(ScreenOffDelayTimeEnum.NEVER, OnScreenOffDelaySet);
|
||||
PXR_Enterprise.SwitchSystemFunction(SystemFunctionSwitchEnum.SFS_AUTOSLEEP, SwitchEnum.S_OFF);
|
||||
|
||||
picoSettingsApplied = true;
|
||||
lastPicoError = string.Empty;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
lastPicoError = exception.Message;
|
||||
}
|
||||
#else
|
||||
picoSettingsApplied = true;
|
||||
#endif
|
||||
RefreshStatus();
|
||||
}
|
||||
|
||||
private void CapturePicoSystemDelays()
|
||||
{
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
if (picoSystemDelaysCaptured || !enterpriseBound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
previousSleepDelay = PXR_Enterprise.GetSleepDelay();
|
||||
previousScreenOffDelay = PXR_Enterprise.GetScreenOffDelay();
|
||||
picoSystemDelaysCaptured = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnScreenOffDelaySet(int result)
|
||||
{
|
||||
lastScreenOffDelayResult = result;
|
||||
RefreshStatus();
|
||||
}
|
||||
|
||||
private void ReleasePicoEnterpriseWakeLock()
|
||||
{
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
if (!enterpriseInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PXR_Enterprise.ReleaseWakeLock();
|
||||
RestorePicoSystemDelays();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
lastPicoError = exception.Message;
|
||||
}
|
||||
#endif
|
||||
picoSettingsApplied = false;
|
||||
lastScreenOffDelayResult = int.MinValue;
|
||||
}
|
||||
|
||||
private void RestorePicoSystemDelays()
|
||||
{
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
if (!picoSystemDelaysCaptured)
|
||||
{
|
||||
PXR_Enterprise.SwitchSystemFunction(SystemFunctionSwitchEnum.SFS_AUTOSLEEP, SwitchEnum.S_ON);
|
||||
return;
|
||||
}
|
||||
|
||||
PXR_Enterprise.PropertySetSleepDelay(previousSleepDelay);
|
||||
PXR_Enterprise.PropertySetScreenOffDelay(previousScreenOffDelay, null);
|
||||
PXR_Enterprise.SwitchSystemFunction(SystemFunctionSwitchEnum.SFS_AUTOSLEEP, SwitchEnum.S_ON);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void RefreshStatus()
|
||||
{
|
||||
if (!keepAwake)
|
||||
{
|
||||
StatusText = "KeepAwake disabled";
|
||||
return;
|
||||
}
|
||||
|
||||
if (applyWhenSendingOnly && sender != null && !sender.SendEnabled)
|
||||
{
|
||||
StatusText = "KeepAwake waiting";
|
||||
return;
|
||||
}
|
||||
|
||||
string activeState = KeepAwakeActive ? "ON" : "OFF";
|
||||
string picoState = "PICO --";
|
||||
if (usePicoEnterpriseApi)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(lastPicoError))
|
||||
{
|
||||
picoState = "PICO error";
|
||||
}
|
||||
else if (enterpriseBindingRequested && !enterpriseInitialized)
|
||||
{
|
||||
picoState = "PICO init";
|
||||
}
|
||||
else if (enterpriseBound)
|
||||
{
|
||||
picoState = picoSettingsApplied ? "PICO applied" : "PICO bound";
|
||||
}
|
||||
else if (enterpriseBindingRequested)
|
||||
{
|
||||
picoState = "PICO binding";
|
||||
}
|
||||
}
|
||||
|
||||
string screenState = lastScreenOffDelayResult == int.MinValue
|
||||
? string.Empty
|
||||
: " screen " + lastScreenOffDelayResult.ToString();
|
||||
|
||||
StatusText = "KeepAwake " + activeState + " | " + picoState + screenState;
|
||||
}
|
||||
|
||||
private void SetAndroidKeepScreenOn(bool enabled)
|
||||
{
|
||||
if (!useAndroidWindowFlag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
try
|
||||
{
|
||||
using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
||||
{
|
||||
AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
|
||||
activity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
|
||||
{
|
||||
AndroidJavaObject window = activity.Call<AndroidJavaObject>("getWindow");
|
||||
if (enabled)
|
||||
{
|
||||
window.Call("addFlags", AndroidFlagKeepScreenOn);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.Call("clearFlags", AndroidFlagKeepScreenOn);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
lastPicoError = exception.Message;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09740726046b89c1b89aad001e0caa83
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
638
unity/XR_RM_PICO_UDP_Sender/Assets/Scripts/PicoUdpConfigPanel.cs
Executable file
638
unity/XR_RM_PICO_UDP_Sender/Assets/Scripts/PicoUdpConfigPanel.cs
Executable file
@ -0,0 +1,638 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.XR;
|
||||
|
||||
public class PicoUdpConfigPanel : MonoBehaviour
|
||||
{
|
||||
private const int RowCount = 6;
|
||||
private const float AxisRepeatDelay = 0.22f;
|
||||
|
||||
public int targetFrameRate = 90;
|
||||
public bool showOnStart = true;
|
||||
public bool togglePanelWithMenuButton = true;
|
||||
|
||||
private readonly Text[] rowTexts = new Text[RowCount];
|
||||
private readonly Color normalColor = new Color(0.86f, 0.91f, 0.96f, 1.0f);
|
||||
private readonly Color selectedColor = new Color(0.35f, 0.82f, 1.0f, 1.0f);
|
||||
private readonly Color mutedColor = new Color(0.58f, 0.66f, 0.74f, 1.0f);
|
||||
|
||||
private PicoControllerUdpSender sender;
|
||||
private Canvas canvas;
|
||||
private GameObject configPanelObject;
|
||||
private GameObject runHudObject;
|
||||
private Text titleText;
|
||||
private Text statusText;
|
||||
private Text footerText;
|
||||
private Text hudTitleText;
|
||||
private Text hudStatusText;
|
||||
private Text hudDetailText;
|
||||
private Text hudHintText;
|
||||
private TouchScreenKeyboard keyboard;
|
||||
private KeyboardTarget keyboardTarget;
|
||||
private string hostDraft;
|
||||
private int portDraft;
|
||||
private float sendHzDraft;
|
||||
private bool convertDraft;
|
||||
private PicoKeepAwake keepAwake;
|
||||
private int selectedRow;
|
||||
private float nextAxisTime;
|
||||
private bool wasActivatePressed;
|
||||
private bool wasApplyPressed;
|
||||
private bool wasMenuPressed;
|
||||
private string panelMessage = "Ready";
|
||||
|
||||
private enum KeyboardTarget
|
||||
{
|
||||
None,
|
||||
Host,
|
||||
Port,
|
||||
SendHz
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
sender = GetComponent<PicoControllerUdpSender>();
|
||||
if (sender == null)
|
||||
{
|
||||
sender = gameObject.AddComponent<PicoControllerUdpSender>();
|
||||
}
|
||||
|
||||
keepAwake = GetComponent<PicoKeepAwake>();
|
||||
if (keepAwake == null)
|
||||
{
|
||||
keepAwake = gameObject.AddComponent<PicoKeepAwake>();
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
ApplyRuntimeDefaults();
|
||||
EnsurePanel();
|
||||
LoadDraftsFromSender();
|
||||
canvas.gameObject.SetActive(true);
|
||||
SetConfigPanelVisible(showOnStart, false);
|
||||
RefreshPanel();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
HandleKeyboard();
|
||||
HandleControllerInput();
|
||||
RefreshPanel();
|
||||
}
|
||||
|
||||
private void ApplyRuntimeDefaults()
|
||||
{
|
||||
Application.targetFrameRate = targetFrameRate;
|
||||
Application.runInBackground = true;
|
||||
QualitySettings.vSyncCount = 0;
|
||||
Screen.sleepTimeout = SleepTimeout.NeverSleep;
|
||||
XRSettings.eyeTextureResolutionScale = 1.0f;
|
||||
}
|
||||
|
||||
private void EnsurePanel()
|
||||
{
|
||||
Camera panelCamera = Camera.main;
|
||||
if (panelCamera == null)
|
||||
{
|
||||
GameObject cameraObject = new GameObject("XR-RM Main Camera");
|
||||
cameraObject.tag = "MainCamera";
|
||||
panelCamera = cameraObject.AddComponent<Camera>();
|
||||
panelCamera.nearClipPlane = 0.05f;
|
||||
panelCamera.farClipPlane = 100.0f;
|
||||
panelCamera.clearFlags = CameraClearFlags.SolidColor;
|
||||
panelCamera.backgroundColor = new Color(0.02f, 0.025f, 0.03f, 1.0f);
|
||||
|
||||
if (FindObjectOfType<AudioListener>() == null)
|
||||
{
|
||||
cameraObject.AddComponent<AudioListener>();
|
||||
}
|
||||
|
||||
AddTrackedPoseDriverIfAvailable(cameraObject);
|
||||
}
|
||||
|
||||
GameObject canvasObject = new GameObject("XR-RM UDP Config Canvas", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler));
|
||||
canvasObject.transform.SetParent(panelCamera.transform, false);
|
||||
canvasObject.transform.localPosition = new Vector3(0.0f, 0.0f, 2.0f);
|
||||
canvasObject.transform.localRotation = Quaternion.identity;
|
||||
canvasObject.transform.localScale = Vector3.one * 0.0014f;
|
||||
|
||||
canvas = canvasObject.GetComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.WorldSpace;
|
||||
canvas.worldCamera = panelCamera;
|
||||
canvas.sortingOrder = 10;
|
||||
|
||||
RectTransform canvasRect = canvasObject.GetComponent<RectTransform>();
|
||||
canvasRect.sizeDelta = new Vector2(980.0f, 640.0f);
|
||||
|
||||
CanvasScaler scaler = canvasObject.GetComponent<CanvasScaler>();
|
||||
scaler.dynamicPixelsPerUnit = 12.0f;
|
||||
scaler.referencePixelsPerUnit = 100.0f;
|
||||
|
||||
configPanelObject = new GameObject("Config Panel", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
configPanelObject.transform.SetParent(canvasObject.transform, false);
|
||||
RectTransform backgroundRect = configPanelObject.GetComponent<RectTransform>();
|
||||
backgroundRect.anchorMin = Vector2.zero;
|
||||
backgroundRect.anchorMax = Vector2.one;
|
||||
backgroundRect.offsetMin = Vector2.zero;
|
||||
backgroundRect.offsetMax = Vector2.zero;
|
||||
Image background = configPanelObject.GetComponent<Image>();
|
||||
background.color = new Color(0.045f, 0.06f, 0.075f, 0.94f);
|
||||
|
||||
titleText = CreateText("Title", configPanelObject.transform, 38, FontStyle.Bold, TextAnchor.MiddleLeft);
|
||||
SetRect(titleText.rectTransform, new Vector2(0.06f, 0.82f), new Vector2(0.94f, 0.96f), Vector2.zero, Vector2.zero);
|
||||
|
||||
statusText = CreateText("Status", configPanelObject.transform, 22, FontStyle.Normal, TextAnchor.MiddleLeft);
|
||||
statusText.color = mutedColor;
|
||||
SetRect(statusText.rectTransform, new Vector2(0.06f, 0.72f), new Vector2(0.94f, 0.82f), Vector2.zero, Vector2.zero);
|
||||
|
||||
for (int i = 0; i < RowCount; i++)
|
||||
{
|
||||
Text rowText = CreateText("Row " + i, configPanelObject.transform, 27, FontStyle.Normal, TextAnchor.MiddleLeft);
|
||||
float top = 0.68f - i * 0.09f;
|
||||
SetRect(rowText.rectTransform, new Vector2(0.08f, top - 0.075f), new Vector2(0.92f, top), Vector2.zero, Vector2.zero);
|
||||
rowTexts[i] = rowText;
|
||||
}
|
||||
|
||||
footerText = CreateText("Footer", configPanelObject.transform, 23, FontStyle.Normal, TextAnchor.MiddleCenter);
|
||||
footerText.color = normalColor;
|
||||
SetRect(footerText.rectTransform, new Vector2(0.05f, 0.025f), new Vector2(0.95f, 0.15f), Vector2.zero, Vector2.zero);
|
||||
|
||||
CreateRunHud(canvasObject.transform);
|
||||
}
|
||||
|
||||
private void CreateRunHud(Transform canvasParent)
|
||||
{
|
||||
runHudObject = new GameObject("Run HUD", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
runHudObject.transform.SetParent(canvasParent, false);
|
||||
RectTransform hudRect = runHudObject.GetComponent<RectTransform>();
|
||||
hudRect.anchorMin = Vector2.zero;
|
||||
hudRect.anchorMax = Vector2.one;
|
||||
hudRect.offsetMin = Vector2.zero;
|
||||
hudRect.offsetMax = Vector2.zero;
|
||||
Image hudBackground = runHudObject.GetComponent<Image>();
|
||||
hudBackground.color = new Color(0.018f, 0.026f, 0.032f, 0.88f);
|
||||
|
||||
GameObject headerObject = CreateImage("HUD Header", runHudObject.transform, new Color(0.035f, 0.05f, 0.06f, 0.88f));
|
||||
SetRect(headerObject.GetComponent<RectTransform>(), new Vector2(0.05f, 0.70f), new Vector2(0.95f, 0.92f), Vector2.zero, Vector2.zero);
|
||||
|
||||
hudTitleText = CreateText("HUD Title", headerObject.transform, 34, FontStyle.Bold, TextAnchor.MiddleLeft);
|
||||
SetRect(hudTitleText.rectTransform, new Vector2(0.04f, 0.54f), new Vector2(0.96f, 0.95f), Vector2.zero, Vector2.zero);
|
||||
|
||||
hudStatusText = CreateText("HUD Status", headerObject.transform, 28, FontStyle.Bold, TextAnchor.MiddleLeft);
|
||||
hudStatusText.color = selectedColor;
|
||||
SetRect(hudStatusText.rectTransform, new Vector2(0.04f, 0.15f), new Vector2(0.96f, 0.56f), Vector2.zero, Vector2.zero);
|
||||
|
||||
hudDetailText = CreateText("HUD Detail", runHudObject.transform, 25, FontStyle.Normal, TextAnchor.MiddleCenter);
|
||||
SetRect(hudDetailText.rectTransform, new Vector2(0.07f, 0.36f), new Vector2(0.93f, 0.64f), Vector2.zero, Vector2.zero);
|
||||
|
||||
hudHintText = CreateText("HUD Hint", runHudObject.transform, 23, FontStyle.Normal, TextAnchor.MiddleCenter);
|
||||
hudHintText.color = mutedColor;
|
||||
SetRect(hudHintText.rectTransform, new Vector2(0.07f, 0.08f), new Vector2(0.93f, 0.20f), Vector2.zero, Vector2.zero);
|
||||
|
||||
GameObject verticalLine = CreateImage("HUD Vertical Reticle", runHudObject.transform, new Color(0.35f, 0.82f, 1.0f, 0.38f));
|
||||
SetRect(verticalLine.GetComponent<RectTransform>(), new Vector2(0.498f, 0.27f), new Vector2(0.502f, 0.36f), Vector2.zero, Vector2.zero);
|
||||
|
||||
GameObject horizontalLine = CreateImage("HUD Horizontal Reticle", runHudObject.transform, new Color(0.35f, 0.82f, 1.0f, 0.38f));
|
||||
SetRect(horizontalLine.GetComponent<RectTransform>(), new Vector2(0.47f, 0.313f), new Vector2(0.53f, 0.319f), Vector2.zero, Vector2.zero);
|
||||
}
|
||||
|
||||
private static void AddTrackedPoseDriverIfAvailable(GameObject cameraObject)
|
||||
{
|
||||
string[] typeNames =
|
||||
{
|
||||
"UnityEngine.SpatialTracking.TrackedPoseDriver, UnityEngine.SpatialTracking",
|
||||
"UnityEngine.InputSystem.XR.TrackedPoseDriver, Unity.InputSystem"
|
||||
};
|
||||
|
||||
for (int i = 0; i < typeNames.Length; i++)
|
||||
{
|
||||
Type driverType = Type.GetType(typeNames[i]);
|
||||
if (driverType != null && cameraObject.GetComponent(driverType) == null)
|
||||
{
|
||||
cameraObject.AddComponent(driverType);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Text CreateText(string name, Transform parent, int fontSize, FontStyle fontStyle, TextAnchor alignment)
|
||||
{
|
||||
GameObject textObject = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(Text));
|
||||
textObject.transform.SetParent(parent, false);
|
||||
|
||||
Text text = textObject.GetComponent<Text>();
|
||||
text.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
text.fontSize = fontSize;
|
||||
text.fontStyle = fontStyle;
|
||||
text.alignment = alignment;
|
||||
text.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
text.verticalOverflow = VerticalWrapMode.Truncate;
|
||||
text.resizeTextForBestFit = true;
|
||||
text.resizeTextMinSize = Mathf.Max(12, fontSize - 8);
|
||||
text.resizeTextMaxSize = fontSize;
|
||||
text.color = normalColor;
|
||||
return text;
|
||||
}
|
||||
|
||||
private static void SetRect(RectTransform rectTransform, Vector2 anchorMin, Vector2 anchorMax, Vector2 offsetMin, Vector2 offsetMax)
|
||||
{
|
||||
rectTransform.anchorMin = anchorMin;
|
||||
rectTransform.anchorMax = anchorMax;
|
||||
rectTransform.offsetMin = offsetMin;
|
||||
rectTransform.offsetMax = offsetMax;
|
||||
}
|
||||
|
||||
private static GameObject CreateImage(string name, Transform parent, Color color)
|
||||
{
|
||||
GameObject imageObject = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
imageObject.transform.SetParent(parent, false);
|
||||
Image image = imageObject.GetComponent<Image>();
|
||||
image.color = color;
|
||||
return imageObject;
|
||||
}
|
||||
|
||||
private void LoadDraftsFromSender()
|
||||
{
|
||||
hostDraft = sender.host;
|
||||
portDraft = sender.port;
|
||||
sendHzDraft = sender.sendHz;
|
||||
convertDraft = sender.convertUnityToProjectCoordinates;
|
||||
}
|
||||
|
||||
private void HandleKeyboard()
|
||||
{
|
||||
if (keyboard == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyboard.status == TouchScreenKeyboard.Status.Done || keyboard.status == TouchScreenKeyboard.Status.LostFocus)
|
||||
{
|
||||
CommitKeyboardText(keyboard.text);
|
||||
keyboard = null;
|
||||
keyboardTarget = KeyboardTarget.None;
|
||||
}
|
||||
else if (keyboard.status == TouchScreenKeyboard.Status.Canceled)
|
||||
{
|
||||
panelMessage = "Edit canceled";
|
||||
keyboard = null;
|
||||
keyboardTarget = KeyboardTarget.None;
|
||||
}
|
||||
}
|
||||
|
||||
private void CommitKeyboardText(string value)
|
||||
{
|
||||
string trimmed = value == null ? string.Empty : value.Trim();
|
||||
if (keyboardTarget == KeyboardTarget.Host)
|
||||
{
|
||||
hostDraft = trimmed;
|
||||
panelMessage = "Target IP edited";
|
||||
}
|
||||
else if (keyboardTarget == KeyboardTarget.Port)
|
||||
{
|
||||
if (int.TryParse(trimmed, out int parsedPort))
|
||||
{
|
||||
portDraft = Mathf.Clamp(parsedPort, 1, 65535);
|
||||
panelMessage = "Target port edited";
|
||||
}
|
||||
else
|
||||
{
|
||||
panelMessage = "Invalid port text";
|
||||
}
|
||||
}
|
||||
else if (keyboardTarget == KeyboardTarget.SendHz)
|
||||
{
|
||||
if (TryParseFloat(trimmed, out float parsedHz))
|
||||
{
|
||||
sendHzDraft = Mathf.Clamp(parsedHz, 1.0f, 120.0f);
|
||||
panelMessage = "Send rate edited";
|
||||
}
|
||||
else
|
||||
{
|
||||
panelMessage = "Invalid send rate text";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleControllerInput()
|
||||
{
|
||||
bool menuPressed = IsButtonPressed(CommonUsages.menuButton);
|
||||
if (togglePanelWithMenuButton && menuPressed && !wasMenuPressed)
|
||||
{
|
||||
SetConfigPanelVisible(!IsConfigPanelVisible(), true);
|
||||
}
|
||||
|
||||
wasMenuPressed = menuPressed;
|
||||
if (!IsConfigPanelVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 axis = ReadPrimaryAxis();
|
||||
if (Time.unscaledTime >= nextAxisTime)
|
||||
{
|
||||
if (axis.y > 0.55f)
|
||||
{
|
||||
selectedRow = (selectedRow + RowCount - 1) % RowCount;
|
||||
nextAxisTime = Time.unscaledTime + AxisRepeatDelay;
|
||||
}
|
||||
else if (axis.y < -0.55f)
|
||||
{
|
||||
selectedRow = (selectedRow + 1) % RowCount;
|
||||
nextAxisTime = Time.unscaledTime + AxisRepeatDelay;
|
||||
}
|
||||
else if (axis.x > 0.65f)
|
||||
{
|
||||
AdjustSelectedValue(1);
|
||||
nextAxisTime = Time.unscaledTime + AxisRepeatDelay;
|
||||
}
|
||||
else if (axis.x < -0.65f)
|
||||
{
|
||||
AdjustSelectedValue(-1);
|
||||
nextAxisTime = Time.unscaledTime + AxisRepeatDelay;
|
||||
}
|
||||
}
|
||||
|
||||
bool activatePressed = IsActivatePressed();
|
||||
if (activatePressed && !wasActivatePressed)
|
||||
{
|
||||
ActivateSelectedRow();
|
||||
}
|
||||
|
||||
wasActivatePressed = activatePressed;
|
||||
|
||||
bool applyPressed = IsButtonPressed(CommonUsages.secondaryButton);
|
||||
if (applyPressed && !wasApplyPressed)
|
||||
{
|
||||
ApplyDrafts();
|
||||
}
|
||||
|
||||
wasApplyPressed = applyPressed;
|
||||
}
|
||||
|
||||
private void AdjustSelectedValue(int direction)
|
||||
{
|
||||
if (selectedRow == 1)
|
||||
{
|
||||
portDraft = Mathf.Clamp(portDraft + direction, 1, 65535);
|
||||
panelMessage = "Target port adjusted";
|
||||
}
|
||||
else if (selectedRow == 2)
|
||||
{
|
||||
sendHzDraft = Mathf.Clamp(sendHzDraft + direction * 5.0f, 1.0f, 120.0f);
|
||||
panelMessage = "Send rate adjusted";
|
||||
}
|
||||
else if (selectedRow == 3)
|
||||
{
|
||||
convertDraft = !convertDraft;
|
||||
panelMessage = "Coordinate mode toggled";
|
||||
}
|
||||
else if (selectedRow == 4)
|
||||
{
|
||||
ToggleSending();
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivateSelectedRow()
|
||||
{
|
||||
if (selectedRow == 0)
|
||||
{
|
||||
OpenKeyboard(KeyboardTarget.Host, hostDraft, TouchScreenKeyboardType.URL);
|
||||
}
|
||||
else if (selectedRow == 1)
|
||||
{
|
||||
OpenKeyboard(KeyboardTarget.Port, portDraft.ToString(), TouchScreenKeyboardType.NumberPad);
|
||||
}
|
||||
else if (selectedRow == 2)
|
||||
{
|
||||
OpenKeyboard(KeyboardTarget.SendHz, Mathf.RoundToInt(sendHzDraft).ToString(), TouchScreenKeyboardType.NumberPad);
|
||||
}
|
||||
else if (selectedRow == 3)
|
||||
{
|
||||
convertDraft = !convertDraft;
|
||||
panelMessage = "Coordinate mode toggled";
|
||||
}
|
||||
else if (selectedRow == 4)
|
||||
{
|
||||
ToggleSending();
|
||||
}
|
||||
else if (selectedRow == 5)
|
||||
{
|
||||
ApplyDrafts();
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenKeyboard(KeyboardTarget target, string text, TouchScreenKeyboardType keyboardType)
|
||||
{
|
||||
keyboardTarget = target;
|
||||
keyboard = TouchScreenKeyboard.Open(text, keyboardType, false, false, false, false, "XR-RM UDP");
|
||||
panelMessage = "Editing " + target;
|
||||
}
|
||||
|
||||
private void ToggleSending()
|
||||
{
|
||||
sender.SetSending(!sender.SendEnabled);
|
||||
panelMessage = sender.SendEnabled ? "UDP sending enabled" : "UDP sending paused";
|
||||
}
|
||||
|
||||
private void ApplyDrafts()
|
||||
{
|
||||
bool applied = sender.ApplyConfig(hostDraft, portDraft, sendHzDraft, convertDraft, true);
|
||||
panelMessage = applied ? "Saved and applied" : sender.LastError;
|
||||
}
|
||||
|
||||
private bool IsActivatePressed()
|
||||
{
|
||||
return IsButtonPressed(CommonUsages.primaryButton) || IsButtonPressed(CommonUsages.triggerButton) || ReadTriggerValue() > 0.75f;
|
||||
}
|
||||
|
||||
private static Vector2 ReadPrimaryAxis()
|
||||
{
|
||||
if (TryReadPrimaryAxis(XRNode.RightHand, out Vector2 rightAxis))
|
||||
{
|
||||
return rightAxis;
|
||||
}
|
||||
|
||||
if (TryReadPrimaryAxis(XRNode.LeftHand, out Vector2 leftAxis))
|
||||
{
|
||||
return leftAxis;
|
||||
}
|
||||
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
private static bool TryReadPrimaryAxis(XRNode node, out Vector2 axis)
|
||||
{
|
||||
InputDevice device = InputDevices.GetDeviceAtXRNode(node);
|
||||
if (device.isValid && device.TryGetFeatureValue(CommonUsages.primary2DAxis, out axis))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
axis = Vector2.zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsButtonPressed(InputFeatureUsage<bool> usage)
|
||||
{
|
||||
return TryReadButton(XRNode.RightHand, usage) || TryReadButton(XRNode.LeftHand, usage);
|
||||
}
|
||||
|
||||
private static bool TryReadButton(XRNode node, InputFeatureUsage<bool> usage)
|
||||
{
|
||||
InputDevice device = InputDevices.GetDeviceAtXRNode(node);
|
||||
return device.isValid && device.TryGetFeatureValue(usage, out bool pressed) && pressed;
|
||||
}
|
||||
|
||||
private static float ReadTriggerValue()
|
||||
{
|
||||
float rightValue = TryReadTriggerValue(XRNode.RightHand);
|
||||
if (rightValue > 0.0f)
|
||||
{
|
||||
return rightValue;
|
||||
}
|
||||
|
||||
return TryReadTriggerValue(XRNode.LeftHand);
|
||||
}
|
||||
|
||||
private static float TryReadTriggerValue(XRNode node)
|
||||
{
|
||||
InputDevice device = InputDevices.GetDeviceAtXRNode(node);
|
||||
if (device.isValid && device.TryGetFeatureValue(CommonUsages.trigger, out float value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private void RefreshPanel()
|
||||
{
|
||||
if (canvas == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshConfigPanel();
|
||||
RefreshRunHud();
|
||||
}
|
||||
|
||||
private void RefreshConfigPanel()
|
||||
{
|
||||
titleText.text = "XR-RM PICO UDP Sender";
|
||||
statusText.text = BuildConfigStatusText();
|
||||
|
||||
SetRow(0, "Target IP", hostDraft);
|
||||
SetRow(1, "Target Port", portDraft.ToString());
|
||||
SetRow(2, "Send Rate", Mathf.RoundToInt(sendHzDraft) + " Hz");
|
||||
SetRow(3, "Coordinates", convertDraft ? "Project (+Z forward)" : "Unity raw");
|
||||
SetRow(4, "UDP Sending", sender.SendEnabled ? "ON" : "OFF");
|
||||
SetRow(5, "Save & Apply", "press trigger");
|
||||
|
||||
footerText.text = "Stick: select / adjust Trigger, A or X: edit\nB or Y: save Menu: run view";
|
||||
}
|
||||
|
||||
private void RefreshRunHud()
|
||||
{
|
||||
if (runHudObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
hudTitleText.text = "XR-RM UDP Sender";
|
||||
hudStatusText.text = (sender.SendEnabled ? "SENDING" : "PAUSED") + " " + BuildEndpointText();
|
||||
hudDetailText.text = BuildTrackingStateText() + " " + BuildGripStateText() + "\n"
|
||||
+ BuildPacketStateText(false) + " " + BuildKeepAwakeStateText();
|
||||
hudHintText.text = "Menu: config panel";
|
||||
}
|
||||
|
||||
private string BuildConfigStatusText()
|
||||
{
|
||||
string endpointState = BuildEndpointText();
|
||||
string sendState = sender.SendEnabled ? "ON" : "OFF";
|
||||
string trackingState = BuildTrackingStateText();
|
||||
string packetState = BuildPacketStateText(true);
|
||||
string keepAwakeState = BuildKeepAwakeStateText();
|
||||
string error = string.IsNullOrEmpty(sender.LastError) ? panelMessage : sender.LastError;
|
||||
return endpointState + " | " + sendState + " | " + trackingState + " | " + packetState + " | " + keepAwakeState + " | " + error;
|
||||
}
|
||||
|
||||
private string BuildEndpointText()
|
||||
{
|
||||
return sender.HasEndpoint ? sender.host + ":" + sender.port : "not configured";
|
||||
}
|
||||
|
||||
private string BuildTrackingStateText()
|
||||
{
|
||||
return "L " + (sender.LeftDeviceValid ? "ok" : "--") + " / R " + (sender.RightDeviceValid ? "ok" : "--");
|
||||
}
|
||||
|
||||
private string BuildGripStateText()
|
||||
{
|
||||
return "Grip L " + (sender.LeftGripPressed ? "ON" : "--") + " / R " + (sender.RightGripPressed ? "ON" : "--");
|
||||
}
|
||||
|
||||
private string BuildPacketStateText(bool compact)
|
||||
{
|
||||
if (sender.SentPackets == 0)
|
||||
{
|
||||
return compact ? "0 pkts" : "Packets 0";
|
||||
}
|
||||
|
||||
double age = Math.Max(0.0, Time.realtimeSinceStartupAsDouble - sender.LastSendTime);
|
||||
string packetCount = sender.SentPackets.ToString(CultureInfo.InvariantCulture);
|
||||
string lastAge = age.ToString("0.0", CultureInfo.InvariantCulture) + "s";
|
||||
return compact ? packetCount + " pkts " + lastAge : "Packets " + packetCount + " Last " + lastAge;
|
||||
}
|
||||
|
||||
private string BuildKeepAwakeStateText()
|
||||
{
|
||||
if (keepAwake == null)
|
||||
{
|
||||
return "KeepAwake --";
|
||||
}
|
||||
|
||||
return keepAwake.StatusText;
|
||||
}
|
||||
|
||||
private void SetRow(int index, string label, string value)
|
||||
{
|
||||
Text rowText = rowTexts[index];
|
||||
bool selected = index == selectedRow;
|
||||
rowText.color = selected ? selectedColor : normalColor;
|
||||
rowText.fontStyle = selected ? FontStyle.Bold : FontStyle.Normal;
|
||||
rowText.text = (selected ? "> " : " ") + label.PadRight(14) + value;
|
||||
}
|
||||
|
||||
private static bool TryParseFloat(string value, out float parsedValue)
|
||||
{
|
||||
return float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out parsedValue)
|
||||
|| float.TryParse(value, out parsedValue);
|
||||
}
|
||||
|
||||
private bool IsConfigPanelVisible()
|
||||
{
|
||||
return configPanelObject != null && configPanelObject.activeSelf;
|
||||
}
|
||||
|
||||
private void SetConfigPanelVisible(bool visible, bool updateMessage)
|
||||
{
|
||||
if (configPanelObject != null)
|
||||
{
|
||||
configPanelObject.SetActive(visible);
|
||||
}
|
||||
|
||||
if (runHudObject != null)
|
||||
{
|
||||
runHudObject.SetActive(!visible);
|
||||
}
|
||||
|
||||
if (updateMessage)
|
||||
{
|
||||
panelMessage = visible ? "Config panel" : "Run view";
|
||||
}
|
||||
}
|
||||
}
|
||||
11
unity/XR_RM_PICO_UDP_Sender/Assets/Scripts/PicoUdpConfigPanel.cs.meta
Executable file
11
unity/XR_RM_PICO_UDP_Sender/Assets/Scripts/PicoUdpConfigPanel.cs.meta
Executable file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d53b4ec62e44220a350bfe97f87d681
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user