Skip to content

LeapMotionHaptics

セットアップ

unityのプロジェクト作って、

エクスプローラーからHap-Eフォルダを入れる。

projectsettingsから

これらを入れる。

streamingAssetsフォルダ(Packages/Ultraleap Hap-E Haptics/StreamingAssets)をAssetsに動かす。

ScenesフォルダがあるとREADMEに書いてあったが、なかったのでエクスプローラーから探してそれを直接Assetsに入れました。

※TMPを入れること!

デモシーン

●JSONPlayer

https://youtu.be/3PoVFDvAavI

”StreamingAssets/haptics”にはいってるJSONファイルを読み取って再生できるデモらしい。

  • シーンを動かしているコード

    csharp
    using System.Collections.Generic;
    using System.IO;
    using UnityEngine;
    using UnityEngine.UI;
    using TMPro;
    
    namespace HapE.Unity
    {
        public class JSONLoaderDropdown : MonoBehaviour
        {
            public TMP_Dropdown dropdown;
            public TMP_InputField jsonRootInput;
            public Button refreshButton;
            public Button playButton;
            public bool isPlaying = false;
        
            public Sprite playSprite;
            public Sprite pauseSprite;
        
            [SerializeField]
            public string hapticRootPath = "";
            public string currentJSONPath = "";
            public HapEDeviceManager hapticDevice;
            public List<string> jsonFiles = new();
        
            // Start is called before the first frame update
            void Start()
            {
                hapticRootPath = Path.Combine(Application.streamingAssetsPath, "haptics");
                Debug.Log("hapticRootPath: " + hapticRootPath);
                jsonRootInput.text = hapticRootPath;
                LoadJSONFilesFromRootPath();
                jsonRootInput.onEndEdit.AddListener(RootInputFieldChanged);
                dropdown.onValueChanged.AddListener(DropdownChanged);
                dropdown.RefreshShownValue();
            }
        
            public void RootInputFieldChanged(string value)
            {
                hapticRootPath = value;
            }
        
            public void DropdownChanged(int value)
            {
                currentJSONPath = jsonFiles[value];
            }
        
            public void LoadJSONFilesFromRootPath()
            {
                jsonFiles.Clear();
                dropdown.ClearOptions();
                Debug.Log("hapticRootPath:" + hapticRootPath);
                DirectoryInfo dir = new DirectoryInfo(hapticRootPath);
                FileInfo[] info = dir.GetFiles("*.json");
        
                foreach (FileInfo f in info)
                {
                    // For each Row, instantiate a new HaptiRowPrefb and store its data
                    dropdown.options.Add(new TMP_Dropdown.OptionData() { text = f.Name});
                    jsonFiles.Add(f.FullName);
                }
        
                dropdown.RefreshShownValue();
            }
        
            public void PlayButtonPressed()
            {
                if (isPlaying)
                {
                    hapticDevice.StopHaptics();
                    playButton.image.sprite = playSprite;
                }
                else
                {
                    currentJSONPath = jsonFiles[dropdown.value];
                    Debug.Log("Playing :" + currentJSONPath);
                    hapticDevice.PlayHapEJSON(currentJSONPath);
                    playButton.image.sprite = pauseSprite;
                }
                isPlaying = !isPlaying;
            }
        
            // Update is called once per frame
            void Update()
            {
                if (Input.GetKeyUp(KeyCode.Escape))
                {
                    // TODO: raise dialog - are you sure you want to quit?
                    Application.Quit();
                }
            }
        }
    }

●抜粋

csharp
public string currentJSONPath = "";
public HapEDeviceManager hapticDevice;
//~~~~~~~~~~省略~~~~~~~~~~
//再生
hapticDevice.PlayHapEJSON(currentJSONPath);
//停止
hapticDevice.StopHaptics();

hapticDeviceは、HapEDeviceManager型で定義されている。HapEDeviceManagerはUltraleap Hap-E Hapticsフォルダに含まれていました。(冒頭でエクスプローラからPackageに入れたファイル)

  • HapEDeviceMangerのコード(すごく長いです)

    csharp
    using System.Collections.Generic;
    using UnityEngine;
    using System;
    using System.Linq;
    using System.IO;
    
    namespace HapE.Unity
    {
        /// <summary>
        /// The Main interfce to the Hap-E Device. Handles connection and Playback.
        /// TODO: Split out the connection/playback logic
        /// </summary>
        /// 
        using Ultraleap.Haptics;
        using Leap.Unity;
    
        public class HapEDeviceManager : MonoBehaviour
        {
            [Header("Hap-e Device")]
            [Tooltip("If enabled, we attempt to connect to a device on startup")]
            public bool autoConnect = true;
            public Context ctx;
    
            [SerializeField]
            private List<DeviceInfo> availableDevices;
            public Device hapticDevice;
            public bool useMockIfRequired = true;
    
            [SerializeField]
            private bool _muteHaptics;
    
            [SerializeField]
            private float _softStartStopValue = 0f;
            public float SoftStartStopValue
            {
                get { return _softStartStopValue; }
                set { _softStartStopValue = Mathf.Clamp(value, 0.0f, 10f); }
            }
    
            [Tooltip("If true, haptic rendering will still occur, but not output any haptics. Useful for Demo mode.")]
            public bool MuteHaptics
            {
                get { return _muteHaptics; }
                set { SetHapticsMuteEnabled(value); }
            }
    
            public HapEParticleRenderer hapticRenderer;
            public LeapServiceProvider leapServiceProvider;
    
            // A bool which determines whether haptics should be updated every frame
            // (should be set to 'true' for anything that tracking the hand. 'false' if Fixed.
            [SerializeField]
            private bool _updateHaptics;
            public bool UpdateHaptics
            {
                get { return _updateHaptics; }
                set { _updateHaptics = value; }
            }
    
            [Header("Transforms")]
            [Tooltip("The Unity world space transform of the Haptic Array")]
            public Transform arrayTransform;
            private float[] previousTransform = new float[16];
            private float[] trackerTransform = new float[16];
            private TrackingOriginTransform trackingOriginTransform;
            private Vector3 defaultTrackerPosition = new Vector3(0, 0, 0.2f);
    
            [Header("Tracking")]
            public TrackingFixation trackTransformObject;
            public Transform trackerOrigin;
            public bool loadTrackerOriginFromJSON = true;
    
            // Start is called before the first frame update
            void Start()
            {
                if (arrayTransform == null){
                    arrayTransform = transform;
                }
    
                if (loadTrackerOriginFromJSON)
                {
                    SetTrackingOriginFromJSON();
                }
    
                // Don't do anything if we don't want to auto-connect. Useful for multi-array configs.
                if (!autoConnect)
                {
                    return;
                }
    
                ConnectToNewHapEDevice();
                if (hapticDevice == null)
                {
                    Debug.LogWarning("Could not connect to phyiscal Hap-E device or create a MockDevice! Check Hap-e Plugins are up to date.");
                }
    
                // Set up Leap Listener to push tracking frame events to update the HapE Tracker Transform.
                if (leapServiceProvider == null)
                {
                    leapServiceProvider = FindObjectOfType<LeapServiceProvider>();
                }
                leapServiceProvider.GetLeapController().FrameReady += FrameReady;
            }
    
            private void FrameReady(object sender, Leap.FrameEventArgs e)
            {
                if (UpdateHaptics)
                {
                    //Debug.Log($"FrameReady at Time {Time.time} with {e.frame.CurrentFramesPerSecond}");
                    UpdateTrackerTransformFromNewLeapFrame();
                }
            }
    
            private void OnDisable()
            {
                if (hapticDevice != null && hapticDevice.HapE.IsSupported)
                {
                    StopHaptics();
                }
                if (ctx != null)
                {
                    //ctx.Dispose();
                    ctx = null;
                }
            }
    
            #region Tracking
            // Main update loop updates the Hap-e Tracker Transform
            // Main update loop updates the Hap-e Tracker Transform
            void UpdateTrackerTransformFromNewLeapFrame()
            {
                if (trackTransformObject != null)
                {
                    Quaternion rotation = trackTransformObject.transform.localRotation;
                    Vector3 translation = trackTransformObject.transform.localPosition;
                    SetTrackerTransform(rotation, translation);
                }
            }
    
            public void SetTrackerTransform(Quaternion rotation, Vector3 translation)
            {
                var hapticUnityLocalMatrix = Matrix4x4.TRS(translation, rotation, Vector3.one);
                var ulMatrix = UnityToHapticSpace.inverse * (hapticUnityLocalMatrix * UnityToHapticSpace);
                trackerTransform = ToFloatArray(ulMatrix);
    
                // Only update the Tracker if the Transform has changed, no point sending updates for fixed point haptics, only for hand tracked ones.
                if (!Enumerable.SequenceEqual(previousTransform, trackerTransform))
                {
                    try
                    {
                        hapticDevice.HapE.V3SetTracker(trackerTransform);
                        Array.Copy(trackerTransform, previousTransform, 16);
                    }
                    catch (Exception e)
                    {
                        Debug.LogWarning(e);
                        Debug.LogWarning("Unable to update haptics.");
                        UpdateHaptics = false;
                    }
                }
            }
    
            readonly Matrix4x4 UnityToHapticSpace = new Matrix4x4(new Vector4(1f, 0f, 0f, 0f),
                                                                  new Vector4(0f, 0f, 1f, 0f),
                                                                  new Vector4(0f, 1f, 0f, 0f),
                                                                  new Vector4(0f, 0f, 0f, 1f));
    
            float[] ToFloatArray(Matrix4x4 inMatrix)
            {
                return new float[16]
                {
                    inMatrix.m00, inMatrix.m01, inMatrix.m02, inMatrix.m03,
                    inMatrix.m10, inMatrix.m11, inMatrix.m12, inMatrix.m13,
                    inMatrix.m20, inMatrix.m21, inMatrix.m22, inMatrix.m23,
                    inMatrix.m30, inMatrix.m31, inMatrix.m32, inMatrix.m33
                };
            }
    
            /// <summary>
            /// If requested, set the HandTracker Origin to load from JSON at runtime.
            /// </summary>
            private void SetTrackingOriginFromJSON()
            {
                trackingOriginTransform = new();
                string file = Path.Combine(Application.streamingAssetsPath, trackingOriginTransform.defaultTrackerOriginJSONFilename);
                if (!File.Exists(file))
                {
                    Debug.LogWarning("Unable to detect a TrackerOrigin. Assuming Leap Tracker position is x=0,y=0,z=121");
                }
                else
                {
                    string trackingOriginJSON = File.ReadAllText(file);
                    trackingOriginTransform = JsonUtility.FromJson<TrackingOriginTransform>(trackingOriginJSON);
                }
    
                if (trackingOriginTransform.trackingPosition.Count == 3 && trackingOriginTransform.trackingRotation.Count == 3)
                {
                    float xPos = trackingOriginTransform.trackingPosition[0];
                    float yPos = trackingOriginTransform.trackingPosition[1];
                    float zPos = trackingOriginTransform.trackingPosition[2];
    
                    float xRot = trackingOriginTransform.trackingRotation[0];
                    float yRot = trackingOriginTransform.trackingRotation[1];
                    float zRot = trackingOriginTransform.trackingRotation[2];
    
                    Vector3 pos = new Vector3(xPos, yPos, zPos);
                    Quaternion rot = Quaternion.Euler(new Vector3(xRot, yRot, zRot));
                    trackerOrigin.SetPositionAndRotation(pos, rot);
                }
    
                if (trackingOriginTransform.arrayPosition.Count == 3 && trackingOriginTransform.arrayRotation.Count == 3)
                {
                    float xPos = trackingOriginTransform.arrayPosition[0];
                    float yPos = trackingOriginTransform.arrayPosition[1];
                    float zPos = trackingOriginTransform.arrayPosition[2];
    
                    float xRot = trackingOriginTransform.arrayRotation[0];
                    float yRot = trackingOriginTransform.arrayRotation[1];
                    float zRot = trackingOriginTransform.arrayRotation[2];
    
                    Vector3 pos = new Vector3(xPos, yPos, zPos);
                    Quaternion rot = Quaternion.Euler(new Vector3(xRot, yRot, zRot));
                    arrayTransform.SetPositionAndRotation(pos, rot);
                }
            }
    
            /// <summary>
            /// Set the default tracker transform (20 cm above array)
            /// </summary>
            public void SetDefaultTrackerTransform()
            {
                if (hapticDevice != null)
                {
                    trackerTransform[0] = 1f;
                    trackerTransform[3] = defaultTrackerPosition.x;
                    trackerTransform[5] = 1f;
                    trackerTransform[7] = defaultTrackerPosition.y;
                    trackerTransform[10] = 1f;
                    trackerTransform[11] = defaultTrackerPosition.z;
                    trackerTransform[15] = 1f;
                    hapticDevice.HapE.V3SetTracker(trackerTransform);
                }
            }
    
            public void SetHapEMasterVolume(float value)
            {
                if (hapticDevice != null)
                {
                    Debug.Log("Setting Master volume to: " + value);
                    hapticDevice.HapE.V3EnvelopeSetMasterVolume(value);
                }
            }
    
            #endregion
            #region Connection Handling
    
            /// <summary>
            /// Sets whether the device should FORCE use a Mock device, i.e. not output
            /// haptics, but still do evaluation, with a Mock device, (for visualisation purposes).
            /// </summary>
            /// <param name="muted"></param>
            public void SetHapticsMuteEnabled(bool muted)
            {
                _muteHaptics = muted;
                if (muted)
                {
                    if (!UsingMockHapEDevice())
                    {
                        StopHaptics();
                        hapticDevice = ctx.CreateMockDevice();
                    }
                }
                else
                {
                    if (UsingMockHapEDevice())
                    {
                        // Try and get a real device...
                        ConnectToNewHapEDevice();
                    }
                }
            }
    
            public bool UsingMockHapEDevice()
            {
                if (hapticDevice == null)
                {
                    return false;
                }
                return hapticDevice.GetType() == typeof(MockDevice);
            }
    
            public List<DeviceInfo> GetConnectedDevices()
            {
                if (ctx == null)
                {
                    ctx = new Context();
                }
                return ctx.GetConnectedDevices();
            }
    
            /// <summary>
            /// Returns the number of currently connected Haptic Devices
            /// </summary>
            /// <returns></returns>
            public int GetDeviceCount()
            {
                return GetConnectedDevices().Count;
            }
    
            public Device ConnectToNewHapEDevice()
            {
                if (ctx == null)
                {
                    ctx = new Context();
                }
    
                availableDevices = GetConnectedDevices();
                // Ensure haptics are not playing!
                StopHaptics();
    
                if (availableDevices.Count == 0)
                {
                    if (hapticDevice != null)
                    {
                        hapticDevice.Dispose();
                        hapticDevice = null;
                    }
    
                    if (useMockIfRequired && !UsingMockHapEDevice())
                    {
                        hapticDevice = ctx.CreateMockDevice();
                    }
    
                    // Return now, because no phyiscal devices were detected.
                    return null;
                }
    
                // If we're here, we might have already created a MockDevice, so get rid of it first and stop if playing..
                if (hapticDevice != null)
                {
                    hapticDevice.Dispose();
                    hapticDevice = null;
                }
    
                foreach (DeviceInfo di in availableDevices)
                {
                    try
                    {
                        // Get the first connected device...
                        hapticDevice = di.Open();
                        string deviceInfo = GetInfoStringForDevice(hapticDevice);
                        Debug.Log(deviceInfo);
                        break;
                    }
                    catch (Exception e)
                    {
                        Debug.Log("Exception: " + e.ToString());
                    }
                }
                return hapticDevice;
            }
    
            public string HapEDeviceConnectionStatusString()
            {
                string connectionStatusString = "Connection unavailable";
                if (hapticDevice != null)
                {
                    string hardwareFirmware = GetFirmwareString();
                    connectionStatusString = $"Connected to Hap-E Device:\n {hapticDevice.Identifier} \n Firmware: {hardwareFirmware}";
                }
                return connectionStatusString;
            }
    
            /// <summary>
            /// This is the Firmware String we use in the Sensation Designer array connection tooltip.
            /// </summary>
            /// <returns></returns>
            public string GetFirmwareString()
            {
                string firmwareString = "Unknown";
                if (hapticDevice != null)
                {
                    try
                    {
                        var firmwareVersions = hapticDevice.getFirmwareVersions();
                        firmwareString = $"{firmwareVersions.HapticsApp.Major}.{firmwareVersions.HapticsApp.Minor}.{firmwareVersions.HapticsApp.Patch}";
                    }
                    catch (Exception e)
                    {
                        Debug.LogWarning($"Unable to obtain firmware version: {e}");
                    }
                }
    
                return firmwareString;
            }
    
            public void CopyAllHardwareInfoToClipboard()
            {
                if (hapticDevice != null)
                {
                    string hardwareInfoString = GetInfoStringForDevice(hapticDevice);
                    hardwareInfoString.CopyToClipboard();
                }
            }
    
            /// <summary>
            /// Returns a string of all the available Hardware info (firmware etc.) about the Hap-e Device.
            /// </summary>
            /// <returns></returns>
            public string GetInfoStringForDevice(Device device)
            {
                string deviceInfo = "Ultraleap Hap-e Device Info\n" +
                    $"Identifier: {hapticDevice.Identifier}\n";
                if (device != null)
                {
                    try
                    {
                        var firmwareVersions = device.getFirmwareVersions();
                        var hapticsApp = firmwareVersions.HapticsApp;
                        var bootloader = firmwareVersions.Bootloader;
                        var dfu = firmwareVersions.DFUFlashloader;
                        var hardware = firmwareVersions.HardwareVersion;
                        var fpgaProgram = firmwareVersions.FPGAProgrammer;
                        var fpgaSolver = firmwareVersions.FPGASolver;
    
                        deviceInfo += $"HapticsApp: {hapticsApp.Major}.{hapticsApp.Minor}.{hapticsApp.Patch} (#{hapticsApp.GitHash.HashAsHex()}, Dirty={hapticsApp.Dirty}, Valid={hapticsApp.Valid})\n" +
                            $"Bootloader: {bootloader.Major}.{bootloader.Minor}.{bootloader.Patch} (#{bootloader.GitHash.HashAsHex()}, Dirty={bootloader.Dirty}, Valid={bootloader.Valid})\n" +
                            $"DFUFlashloader: {dfu.Major}.{dfu.Minor}.{dfu.Patch} (#{dfu.GitHash.HashAsHex()}, Dirty={dfu.Dirty}, Valid={dfu.Valid})\n" +
                            $"HardwareVersion: {hardware.Major}.{hardware.Minor}.{hardware.Patch} (#{hardware.GitHash.HashAsHex()}, Dirty={hardware.Dirty}, Valid={hardware.Valid})\n" +
                            $"FPGAProgrammer: {fpgaProgram.Major}.{fpgaProgram.Minor}.{fpgaProgram.Patch} (#{fpgaProgram.GitHash.HashAsHex()}, Dirty={fpgaProgram.Dirty}, Valid={fpgaProgram.Valid})\n" +
                            $"FPGASolver: {fpgaSolver.Major}.{fpgaSolver.Minor}.{fpgaSolver.Patch} (#{fpgaSolver.GitHash.HashAsHex()}, Dirty={fpgaSolver.Dirty}, Valid={fpgaSolver.Valid})\n";
                    }
                    catch (Exception e)
                    {
                        Debug.LogWarning($"Unable to obtain firmware version: {e}");
                    }
                }
    
                return deviceInfo;
            }
    
            #endregion
            #region Playback
    
            /// <summary>
            /// Plays a Hap-E json file, as defined by the jsonPath
            /// </summary>
            /// <param name="jsonPath"></param>
            public void PlayHapEJSON(string jsonPath)
            {
                if (hapticDevice == null)
                {
                    Debug.LogWarning("Unable to Play HapEJSON, no valid hapticDevice available!");
                    return;
                }
    
                if (File.Exists(jsonPath))
                {
                    UpdateHaptics = true;
                    hapticDevice.HapE.V3LoadJSONFile(jsonPath);
                    hapticDevice.HapE.Start(softValue: SoftStartStopValue);
                }
                else
                {
                    Debug.LogWarning("JSON Path could not be found:" + jsonPath);
                }
            }
    
            public void PlayHapEJSONOnDevice(string jsonPath, Device hapeDevice)
            {
                if (hapeDevice == null || !hapeDevice.IsConnected || !hapeDevice.HapE.IsSupported)
                {
                    Debug.LogWarning("Unable to Play HapEJSON, no valid hapticDevice available!");
                    return;
                }
    
                if (File.Exists(jsonPath))
                {
                    UpdateHaptics = true;
                    hapeDevice.HapE.V3LoadJSONFile(jsonPath);
                    hapeDevice.HapE.Start(softValue: SoftStartStopValue);
                }
                else
                {
                    Debug.LogWarning("JSON Path could not be found:" + jsonPath);
                }
            }
    
            /// <summary>
            /// Plays a Hap-E haptic definition from a string containing the Hap-e JSON contents.
            /// </summary>
            /// <param name="jsonPath"></param>
            public void PlayHapEJSONString(string jsonString)
            {
                if (hapticDevice == null)
                {
                    Debug.LogWarning("Unable to Play HapEJSON, no valid hapticDevice available!");
                    return;
                }
                UpdateHaptics = true;
                hapticDevice.HapE.V3LoadJSONString(jsonString);
                hapticDevice.HapE.Start(softValue: SoftStartStopValue);
            }
    
            public void SetHapEPlaybackMode(int playbackMode)
            {
                // 0=Forward, 1=Backward, 2=PingPong, 3=Random
                StopHaptics();
                hapticDevice.HapE.V3PainterSetMode((HapE.V3PainterMode)(byte)playbackMode);
                hapticDevice.HapE.V3EnvelopeSetMode((HapE.V3EnvelopeMode)(byte)playbackMode);
            }
    
            public virtual void StopHaptics()
            {
                UpdateHaptics = false;
                hapticRenderer.ClearParticles();
    
                // We have to wrap this in a try catch, because if the device gets disconnected,
                // there's no signal to allow us to null the hapticDevice.
                try
                {
                    hapticDevice?.HapE.Stop();
                }
                catch (Exception e)
                {
                    Debug.LogWarning($"Error Stopping {hapticDevice}. Message = {e.Message}");
                    Debug.LogWarning("Unable to Stop Haptics... Was the device disconnected?");
                }
            }
    
            public void PlayStaticPointFromHapEData(HapEData hapEData)
            {
                // Set Primitive Params
                SetPrimitivePropertiesFromHapEData(hapEData);
                hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnablePainter, 1);
    
                PainterNode point;
                if (hapEData.painter == null || hapEData.painter.nodes == null)
                {
                    Debug.LogWarning("No Node data existed in hapEData! Assuming point at 0,0");
                    point.x = 0f;
                    point.y = 0f;
                    point.x_scale = 1f;
                    point.y_scale = 1f;
                    point.z_rotation = 0;
                }
                else
                {
                    point = hapEData.painter.nodes[0];
                }
                hapticDevice.HapE.V3PainterUpdateNode(0, point.x, point.y, point.z_rotation, point.x_scale, point.y_scale, 1f);
                hapticDevice.HapE.V3PainterUpdateNode(1, point.x, point.y, point.z_rotation, point.x_scale, point.y_scale, 1f);
                hapticDevice.HapE.V3PainterSetLength(1);
                hapticDevice.HapE.V3PainterSetRepeatCount(0);
                SetEnvelopeNodesFromHapEData(hapEData);
    
                if (hapEData.animator != null && hapEData.animator.enabled)
                {
                    SetAnimatorTransformsFromHapEData(hapEData);
                    hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableAnimator, 1);
                }
                else
                {
                    hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableAnimator, 0);
                }
    
                if (!UpdateHaptics)
                {
                    hapticDevice.HapE.Start(softValue: SoftStartStopValue);
                }
                UpdateHaptics = true;
                return;
            }
    
            public void SetPlaybackRepeatCount(int repeatCount)
            {
                if (hapticDevice == null)
                {
                    return;
                }
                hapticDevice.HapE.V3PainterSetRepeatCount((uint)repeatCount);
                hapticDevice.HapE.V3EnvelopeSetRepeatCount((uint)repeatCount);
            }
            #endregion
            #region HapE Data Handling
            public void ResetHapEState()
            {
                hapticDevice?.HapE.V3ResetToDefault();
            }
    
            public bool HapEDeviceAvailable()
            {
                return (hapticDevice != null);// (hapticDevice != null && ((GetConnectedDevices().Count) > 0 || UsingMockHapEDevice()));
            }
    
            /// <summary>
            /// Sets all Primitive Properties, stored in hapEData
            /// </summary>
            /// <param name="hapEData"></param>
            public void SetPrimitivePropertiesFromHapEData(HapEData hapEData)
            {
                if (!HapEDeviceAvailable())
                {
                    return;
                }
    
                if (hapEData.primitive != null)
                {
                    PrimitiveProperties primitiveProperties = hapEData.primitive;
                    float f = primitiveProperties.draw_frequency;
                    float A = primitiveProperties.A;
                    float B = primitiveProperties.B;
                    hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.DrawFrequency, f);
                    hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigA, A);
                    hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigB, B);
                    hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.a, (float)primitiveProperties.a);
                    hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.b, (float)primitiveProperties.b);
    
                    // These properties are not always set in our DefaultBrush Assets, hence they could be null/NaN
                    // TODO: Include null checks for every property, to be safe?...
                    if ((primitiveProperties.max_t != null) && !float.IsNaN(primitiveProperties.max_t.Value))
                    {
                        hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.MaxT, (float)primitiveProperties.max_t);
                    }
                    if ((primitiveProperties.k != null) && !float.IsNaN(primitiveProperties.k.Value))
                    {
                        hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.k, (float)primitiveProperties.k);
                    }
                    if ((primitiveProperties.d != null) && !float.IsNaN(primitiveProperties.d.Value))
                    {
                        hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.d, (float)primitiveProperties.d);
                    }
                }
                else
                {
                    Debug.LogWarning("HapEData had no valid Primitive Data set!");
                }
            }
    
            /// <summary>
            /// Sets All Primitive Parameters froma a HapEBrush scriptable object
            /// </summary>
            /// <param name="brush"></param>
            public void SetPrimitivePropertiesFromHapEBrushDefault(HapEBrushDefault brush)
            {
                foreach (HapEBrushDefault.HapEPrimitiveParameter P in brush.primitiveParams)
                {
                    hapticDevice.HapE.V3PrimitiveSetParameter(P.parameter, P.value);
                }
            }
    
            /// <summary>
            /// Loads HapEData onto the device. Does not play back.
            /// </summary>
            /// <param name="hapEData"></param>
    
            public void LoadHapEData(HapEData hapEData, bool skipReset = false)
            {
                if (!skipReset)
                {
                    ResetHapEState();
                }
    
                if (hapEData.animator != null)
                {
                    SetAnimatorTransformsFromHapEData(hapEData);
                }
    
                if (hapEData.primitive != null)
                {
                    SetPrimitivePropertiesFromHapEData(hapEData);
                }
    
                if (hapEData.painter != null)
                {
                    UpdatePainterNodesFromHapEData(hapEData);
                }
                if (hapEData.envelope != null)
                {
                    SetEnvelopeNodesFromHapEData(hapEData);
                }
            }
    
            /// <summary>
            /// Plays back a Hap-e Sensation from HapEData Object, typically derived from Hap-e JSON.
            /// </summary>
            /// <param name="hapEData"></param>
            public void PlayHapEData(HapEData hapEData, bool skipReset = false)
            {
                LoadHapEData(hapEData, skipReset: skipReset);
                UpdateHaptics = true;
                if (hapticDevice != null)
                {
                    hapticDevice.HapE.Start(softValue: SoftStartStopValue);
                }
            }
    
            /// <summary>
            /// Plays back a Hap-e Sensation from HapEData Object, typically derived from Hap-e JSON,
            /// Allows override of the soft start stop value.
            /// </summary>
            /// <param name="hapEData"></param>
            public void PlayHapEDataWithSoftStartStop(HapEData hapEData, bool skipReset = false, float softValue = 0.0f)
            {
                LoadHapEData(hapEData, skipReset: skipReset);
                UpdateHaptics = true;
                if (hapticDevice != null)
                {
                    hapticDevice.HapE.Start(softValue: SoftStartStopValue);
                }
            }
    
            /// <summary>
            /// Just plays back what ever is the current Hap-E Data on the device, tracking updates stil occur
            /// </summary>
            /// <param name="hapEData"></param>
            public void PlayCurrentDeviceHapEData()
            {
                if (hapticDevice != null)
                {
                    UpdateHaptics = true;
                    hapticDevice.HapE.Start(softValue: SoftStartStopValue);
                }
            }
    
            /// <summary>
            /// Updates the Painter Nodes on device from HapeData Object
            /// </summary>
            /// <param name="hapEData"></param>
            public void UpdatePainterNodesFromHapEData(HapEData hapEData)
            {
                if (hapticDevice == null)
                {
                    return;
                }
    
                PainterProperties painterProperties = hapEData.painter;
                if (painterProperties == null || !hapEData.painter.enabled)
                {
                    hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnablePainter, 0);
                    return;
                }
    
                uint numPoints = (uint)painterProperties.nodes.Count;
    
                if (numPoints == 1 && UpdateHaptics)
                {
                    PlayStaticPointFromHapEData(hapEData);
                    return;
                }
    
                int painterRepeatCount = (int)painterProperties.repeat_count;
                HapE.V3PainterMode painterMode = painterProperties.PainterMode();
                hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnablePainter, 1);
                List<PainterNode> painterNodes = painterProperties.nodes;
    
                for (int ix = 0; ix < numPoints; ix++)
                {
                    PainterNode pt = painterNodes[ix];
                    uint nodeId = (uint)(ix);
                    //Debug.Log("Setting painter node:" + nodeId + ", time: " + pt.t + ", x: " + pt.x + ", y:" + pt.y);
                    hapticDevice.HapE.V3PainterUpdateNode(nodeId, pt.x, pt.y, pt.z_rotation, pt.x_scale, pt.y_scale, pt.t);
                }
                hapticDevice.HapE.V3PainterSetMode(painterMode);
                hapticDevice.HapE.V3PainterSetLength(numPoints);
                hapticDevice.HapE.V3PainterSetRepeatCount((uint)painterRepeatCount);
            }
    
            public void SetEnvelopeNodesFromHapEData(HapEData hapEData)
            {
                if (hapticDevice == null)
                {
                    return;
                }
                EnvelopeProperties envelopeProperties = hapEData.envelope;
                if (envelopeProperties == null || !hapEData.envelope.enabled)
                {
                    hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableEnvelope, 0);
                    return;
                }
    
                List<EnvelopeNode> envelopeNodes = envelopeProperties.nodes;
                uint envelopeLength = (uint)envelopeNodes.Count;
                hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableEnvelope, 1);
                for (int ix = 0; ix < envelopeLength; ix++)
                {
                    uint nodeId = (uint)(ix);
                    float intensity = envelopeNodes[ix].intensity;
                    float time = envelopeNodes[ix].t;
                    //Debug.Log("Setting envelope node ID: " + nodeId + ", intensity:" + intensity + ", time: " + time);
                    hapticDevice.HapE.V3EnvelopeUpdateNode(nodeId, intensity, time);
                }
    
                // Envelope Settings
                int envelopeRepeatCount = (int)envelopeProperties.repeat_count;
                hapticDevice.HapE.V3EnvelopeSetLength((uint)envelopeLength);
                // This sets the number of times the envelope repeats, 0 means it loops infinitely
                hapticDevice.HapE.V3EnvelopeSetRepeatCount((uint)envelopeRepeatCount);
                HapE.V3EnvelopeMode envelopeMode = envelopeProperties.EnvelopeMode();
                hapticDevice.HapE.V3EnvelopeSetMode(envelopeMode);
            }
    
            public void SetAnimatorTransformsFromHapEData(HapEData hapEData)
            {
                //Debug.Log("HapeDeviceManager: SetAnimatorTransformsFromHapEData");
    
                if (hapticDevice == null)
                {
                    return;
                }
    
                if (hapEData.animator == null || !hapEData.animator.enabled)
                {
                    //Debug.Log("Disabling Animator");
                    hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableAnimator, 0);
                    return;
                }
    
                hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableAnimator, 1);
                if (hapEData.animator.T1 != null && hapEData.animator.T1.Length > 0)
                {
                    hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T1, hapEData.animator.T1);
                }
                if (hapEData.animator.T1a1 != null && hapEData.animator.T1a1.Length > 0)
                {
                    hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T1A1, hapEData.animator.T1a1);
                }
                if (hapEData.animator.T1a2 != null && hapEData.animator.T1a2.Length > 0)
                {
                    hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T1A2, hapEData.animator.T1a2);
                }
                if (hapEData.animator.T2 != null && hapEData.animator.T2.Length > 0)
                {
                    hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T2, hapEData.animator.T2);
                }
                if (hapEData.animator.T2a1 != null && hapEData.animator.T2a1.Length > 0)
                {
                    hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T2A1, hapEData.animator.T2a1);
                }
                if (hapEData.animator.T2a2 != null && hapEData.animator.T2a2.Length > 0)
                {
                    hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T2A2, hapEData.animator.T2a2);
                }
                if (hapEData.animator.T1a1_switch_count != 0)
                {
                    hapticDevice.HapE.V3SendCommand(HapE.V3Command.T1A1SwitchCount, (uint)hapEData.animator.T1a1_switch_count);
                }
                if (hapEData.animator.T1a2_switch_count != 0)
                {
                    hapticDevice.HapE.V3SendCommand(HapE.V3Command.T1A2SwitchCount, (uint)hapEData.animator.T1a2_switch_count);
                }
                if (hapEData.animator.T2a1_switch_count != null)
                {
                    hapticDevice.HapE.V3SendCommand(HapE.V3Command.T2A1SwitchCount, (uint)hapEData.animator.T2a1_switch_count.Value);
                }
                if (hapEData.animator.T2a2_switch_count != null)
                {
                    hapticDevice.HapE.V3SendCommand(HapE.V3Command.T2A2SwitchCount, (uint)hapEData.animator.T2a2_switch_count.Value);
                }
            }
            #endregion
        }
    }

●抜粋

csharp
using Ultraleap.Haptics;
using Leap.Unity;

public class HapEDeviceManager : MonoBehaviour
{
		public Device hapticDevice;
//~~~~~~~~~~~~省略~~~~~~~~~~~~~~~

//シーンから呼び出してたコード(再生)
public void PlayHapEJSON(string jsonPath)
{
    if (hapticDevice == null)
    {
        Debug.LogWarning("Unable to Play HapEJSON, no valid hapticDevice available!");
        return;
    }

    if (File.Exists(jsonPath))
    {
        UpdateHaptics = true;
        hapticDevice.HapE.V3LoadJSONFile(jsonPath);
        //多分ここで再生してる
        hapticDevice.HapE.Start(softValue: SoftStartStopValue);
    }
    else
    {
        Debug.LogWarning("JSON Path could not be found:" + jsonPath);
    }
}

//シーンから呼び出してたコード2(停止)
        public virtual void StopHaptics()
        {
            UpdateHaptics = false;
            hapticRenderer.ClearParticles();

            // We have to wrap this in a try catch, because if the device gets disconnected,
            // there's no signal to allow us to null the hapticDevice.
            try
            {
            //多分ここで停止してる
                hapticDevice?.HapE.Stop();
            }
            catch (Exception e)
            {
                Debug.LogWarning($"Error Stopping {hapticDevice}. Message = {e.Message}");
                Debug.LogWarning("Unable to Stop Haptics... Was the device disconnected?");
            }
        }
}

●HapETest

https://youtu.be/B3A3I68dQAk

後述のコードで制御したモーションを再生、編集するデモ。

  • シーンにおいてあったコード

    csharp
    using System.Collections.Generic;
    using UnityEngine;
    using System;
    using System.IO;
    
    /// <summary>
    /// Hap-E API usage examples
    /// PainterNodes represent the position of pattern.
    /// EnvelopeNodes represent the intensity envelope of the sensation.
    /// See also HapticLibraryPlayer.cs, which plays Sensations serialised out to JSON (from Hap-e Designer for instance)
    /// </summary>
    namespace HapE.Unity
    {
        using Ultraleap.Haptics;
    
        public class HapETest : MonoBehaviour
        {
            public HapEDeviceManager deviceManager;
            public bool useMockIfRequired = true;
    
            public bool playing = false;
            private float[] trackerTransform = new float[16];
            public float x = 0.0f;
            public float y = 0.0f;
            public float z = 0.2f;
    
            public List<Vector3> dummyPath = new List<Vector3>();
    
            [Header("Haptic Params")]
            public float bigA = 0.01f;
            public float bigB = 0.01f;
            public float a = 2f;
            public float b = 2f;
            public float drawFrequency = 40f;
    
            // Start is called before the first frame update
            void Start()
            {
                //deviceManager.ConnectToHapEDevice();
                dummyPath.Add(new Vector3(0.029f, 0.028f, 0f));
                dummyPath.Add(new Vector3(-0.032f, 0f, 0.333f));
                dummyPath.Add(new Vector3(0.036f, -0.022f, 0.667f));
                dummyPath.Add(new Vector3(0.03f, -0.050f, 1f));
            }
    
            public void SetHapticParamsFromBrushDefault(HapEBrushDefault brush)
            {
                deviceManager.ResetHapEState();
                foreach (HapEBrushDefault.HapEPrimitiveParameter primParam in brush.primitiveParams)
                {
                    deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(primParam.parameter, primParam.value);
                }
    
                if (brush.animatorTransforms.Count > 0)
                {
                    foreach (HapEBrushDefault.HapETransform t in brush.animatorTransforms)
                    {
                        deviceManager.hapticDevice.HapE.V3AnimatorSetTransform(t.animatorTransform, t.transformArray);
                    }
                }
                if (brush.hapECommands.Count > 0)
                {
                    foreach (HapEBrushDefault.HapECommand cmd in brush.hapECommands)
                    {
                        deviceManager.hapticDevice.HapE.V3SendCommand(cmd.command, cmd.value);
                    }
                }
    
                uint painterNodeCount = (uint)brush.painterNodes.Count;
                uint painterNodeID = 0;
                if (brush.painterNodes.Count > 0)
                {
                    foreach (HapEBrushDefault.HapEPainterNode node in brush.painterNodes)
                    {
                        deviceManager.hapticDevice.HapE.V3PainterUpdateNode(painterNodeID, node.x, node.y, node.zRotation, node.scaleX, node.scaleY, node.time);
                        painterNodeID += 1;
                    }
                    deviceManager.hapticDevice.HapE.V3PainterSetLength(painterNodeCount);
                    Debug.Log("brush.painterMode:" + brush.painterMode);
                    deviceManager.hapticDevice.HapE.V3PainterSetMode(brush.painterMode);
                    deviceManager.hapticDevice.HapE.V3PainterSetRepeatCount(brush.painterRepeatCount);
                }
    
                uint envelopeNodeCount = (uint)brush.envelopeNodes.Count;
                uint envelopeNodeID = 0;
                if (brush.envelopeNodes.Count > 0)
                {
                    foreach (HapEBrushDefault.HapEEnvelopeNode node in brush.envelopeNodes)
                    {
                        deviceManager.hapticDevice.HapE.V3EnvelopeUpdateNode(envelopeNodeID, node.intensity, node.time);
                        painterNodeID += 1;
                    }
                    deviceManager.hapticDevice.HapE.V3EnvelopeSetLength(envelopeNodeCount);
                    deviceManager.hapticDevice.HapE.V3EnvelopeSetMode(brush.envelopeMode);
                    deviceManager.hapticDevice.HapE.V3PainterSetRepeatCount(brush.envelopeRepeatCount);
                }
                PlayAtFixedLocation();
            }
    
            public void SetCircleParams()
            {
                Debug.Log("Setting 2cm Circle 50Hz");
    
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigA, 0.02f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigB, 0.02f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.DrawFrequency, drawFrequency);
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableAnimator, 0);
                PlayAtFixedLocation();
            }
    
            public void SetLineParams()
            {
                Debug.Log("Setting Fat Line");
                deviceManager.ResetHapEState();
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigA, 0.005f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigB, 0.05f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.DrawFrequency, 50);
                PlayAtFixedLocation();
            }
    
            public void SetDialParams()
            {
                deviceManager.ResetHapEState();
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableAnimator, 1);
                Debug.Log("Setting Dial Params");
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigA, 0.01f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigB, 0.01f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.DrawFrequency, 64);
    
                float[] T1 = MathHelpers.Identity3x3();
                T1[2] = 0.02f;
                deviceManager.hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T1, T1);
    
                float[] T2a1 = MathHelpers.Identity3x3();
                T2a1[0] = 0.992546f;
                T2a1[1] = -0.121869f;
                T2a1[3] = 0.121869f;
                T2a1[4] = 0.992546f;
                deviceManager.hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T2A1, T2a1);
                PlayAtFixedLocation();
            }
    
            public void SetDialParamTime(float time)
            {
                float radius = 0.03f;
                deviceManager.ResetHapEState();
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnablePainter, 1);
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableAnimator, 1);
                Debug.Log("Setting Dial Params for a radius of:" + time.ToString());
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigA, 0.01f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigB, 0.01f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.DrawFrequency, 127);
    
                float[] T1 = MathHelpers.Identity3x3();
                T1[2] = radius;
                deviceManager.hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T1, T1);
                deviceManager.hapticDevice.HapE.V3PainterUpdateNode(0, 0, 0, 0, 1.0f, 1.0f, time);
                deviceManager.hapticDevice.HapE.V3PainterUpdateNode(1, 0, 0, (float)Math.PI * 2, 1.0f, 1.0f, 0.0f);
                deviceManager.hapticDevice.HapE.V3PainterSetLength(2);
                deviceManager.hapticDevice.HapE.V3PainterSetRepeatCount(0);
                PlayAtFixedLocation();
            }
    
            public void SetLineScanParams()
            {
                Debug.Log("Setting Line Scan Params");
                deviceManager.ResetHapEState();
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableAnimator, 1);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigA, 0.04f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigB, 0.001f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.DrawFrequency, 50);
    
                // Scan Up
                float lineLength = 0.008f;
                float[] T1a1 = MathHelpers.Identity3x3();
                T1a1[5] = lineLength / 2;
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.T1A1SwitchCount, 13);
                deviceManager.hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T1A1, T1a1);
    
                // Scan Down
                float[] T1a2 = MathHelpers.Identity3x3();
                T1a2[5] = -lineLength / 2;
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.T1A2SwitchCount, 13);
                deviceManager.hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T1A2, T1a2);
                PlayAtFixedLocation();
            }
    
            public void SetPainterScanParams()
            {
                Debug.Log("Setting Painter Scan Params");
                deviceManager.ResetHapEState();
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableAnimator, 0);
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableEnvelope, 1);
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnablePainter, 1);
    
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigA, 0.04f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigB, 0.001f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.DrawFrequency, 127);
    
                deviceManager.hapticDevice.HapE.V3PainterUpdateNode(0, 0, -0.05f, 0, 1f, 1f, 1f);
                deviceManager.hapticDevice.HapE.V3PainterUpdateNode(1, 0, 0.05f, 0, 1f, 1f, 0f);
                deviceManager.hapticDevice.HapE.V3EnvelopeUpdateNode(0, 1f, 1f);
                deviceManager.hapticDevice.HapE.V3EnvelopeUpdateNode(1, 1f, 0f);
    
                deviceManager.hapticDevice.HapE.V3PainterSetLength(2);
                deviceManager.hapticDevice.HapE.V3EnvelopeSetLength(2);
                deviceManager.hapticDevice.HapE.V3PainterSetRepeatCount(0);
                deviceManager.hapticDevice.HapE.V3EnvelopeSetRepeatCount(0);
                deviceManager.hapticDevice.HapE.V3PainterSetMode(HapE.V3PainterMode.PingPong);
                deviceManager.hapticDevice.HapE.V3EnvelopeSetMode(HapE.V3EnvelopeMode.PingPong);
                PlayAtFixedLocation();
            }
    
            public void SetTriangleParams()
            {
                Debug.Log("Setting Triangle Params");
                deviceManager.ResetHapEState();
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableAnimator, 1);
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnablePainter, 1);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigA, 0.01f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigB, 0.01f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.DrawFrequency, 80);
                deviceManager.hapticDevice.HapE.V3PainterSetRepeatCount(0);
                deviceManager.hapticDevice.HapE.V3PainterSetLength(3);
                deviceManager.hapticDevice.HapE.V3PainterSetMode(HapE.V3PainterMode.Forward);
                deviceManager.hapticDevice.HapE.V3PainterUpdateNode(0, 0.04f, 0, 0, 1, 1, 0.33f);
                deviceManager.hapticDevice.HapE.V3PainterUpdateNode(1, 0, 0.04f, 0, 1, 1, 0.33f);
                deviceManager.hapticDevice.HapE.V3PainterUpdateNode(2, -0.04f, 0, 0, 1, 1, 0.33f);
                PlayAtFixedLocation();
            }
    
            // Allows the Hap-E Designer to set points
            public void SetTracingPath(List<Vector3> points)
            {
                Debug.Log("Setting Triangle Params");
                deviceManager.ResetHapEState();
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableAnimator, 1);
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnablePainter, 1);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigA, 0.01f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigB, 0.01f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.DrawFrequency, 80);
                deviceManager.hapticDevice.HapE.V3PainterSetRepeatCount(0);
    
                uint numPoints = (uint)points.Count;
                deviceManager.hapticDevice.HapE.V3PainterSetLength(numPoints);
                deviceManager.hapticDevice.HapE.V3PainterSetMode(HapE.V3PainterMode.PingPong);
    
                uint ix = 0;
                foreach (Vector3 pt in points)
                {
                    // Hard-code time to be 1 second for the tracing for now
                    deviceManager.hapticDevice.HapE.V3PainterUpdateNode(ix, pt.x, pt.y, 0, 1, 1, 1f);
                    ix++;
                }
                PlayAtFixedLocation();
            }
    
            string GetAssetPath(string assetFilename)
            {
                string hapticRootPath = Path.Combine(Application.streamingAssetsPath, "haptics");
                DirectoryInfo dir = new DirectoryInfo(hapticRootPath);
                FileInfo[] info = dir.GetFiles(assetFilename);
                foreach (FileInfo f in info)
                {
                    return f.ToString(); // only return first one
                }
    
                throw new Exception("File not found");
            }
            public void LoadAndPlayRotorSalutTest()
            {
                deviceManager.PlayHapEJSON(GetAssetPath("Rotor Salut.json"));
            }
    
            public void LoadAndPlayButtonClickTest()
            {
                deviceManager.PlayHapEJSON(GetAssetPath("08.Button Click.json"));
            }
    
            public void LoadAndPlayButtonDoubleClickTest()
            {
                deviceManager.PlayHapEJSON(GetAssetPath("09.Button Double Tap.json"));
            }
    
            public void DoubleTapCircleTest()
            {
                SetDoubleTap();
            }
            // A double-tap Circle haptic
            public void SetDoubleTap(float preDelay = 0f,
                float onTime = 0.33f,
                float offTime = 0.15f,
                float postDelay = 0f)
            {
                Debug.Log("Setting 2cm Circle 50Hz");
                deviceManager.ResetHapEState();
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigA, 0.01f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigB, 0.01f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.DrawFrequency, 80f);
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableEnvelope, 1);
                deviceManager.hapticDevice.HapE.V3EnvelopeSetMode(HapE.V3EnvelopeMode.Forward);
    
                // This sets the number of times the envelope repeats.
                deviceManager.hapticDevice.HapE.V3EnvelopeSetRepeatCount(1);
    
                // This sets the number of nodes in the envelope
                deviceManager.hapticDevice.HapE.V3EnvelopeSetLength(9);
    
                // Set keyframes for: WARMUP-ON-OFF-ON-OFF-COOLDOWN
                deviceManager.hapticDevice.HapE.V3EnvelopeUpdateNode(0, 0, preDelay);
                deviceManager.hapticDevice.HapE.V3EnvelopeUpdateNode(1, 1, 0);
                deviceManager.hapticDevice.HapE.V3EnvelopeUpdateNode(2, 1, onTime);
                deviceManager.hapticDevice.HapE.V3EnvelopeUpdateNode(3, 0, 0);
                deviceManager.hapticDevice.HapE.V3EnvelopeUpdateNode(4, 0, offTime);
                deviceManager.hapticDevice.HapE.V3EnvelopeUpdateNode(5, 1, 0);
                deviceManager.hapticDevice.HapE.V3EnvelopeUpdateNode(6, 1, onTime);
                deviceManager.hapticDevice.HapE.V3EnvelopeUpdateNode(7, 0, 0);
                deviceManager.hapticDevice.HapE.V3EnvelopeUpdateNode(8, 0, postDelay);
    
                PlayAtFixedLocation();
            }
    
            public void PlayAtFixedLocation()
            {
                UpdateTrackerTransform();
                deviceManager.hapticDevice.HapE.Start();
                playing = true;
            }
    
            public void SetCircleOpenParams()
            {
                Debug.Log("Setting Circle Open Params");
                deviceManager.ResetHapEState();
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.EnableAnimator, 1);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigA, 0.02f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigB, 0.02f);
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.DrawFrequency, 50);
    
                float scaleValue = 0.9f;
    
                // Scale Down
                float[] T1a1 = new float[9];
                T1a1[0] = 1 / scaleValue;
                T1a1[4] = 1 / scaleValue;
                T1a1[8] = 1f;
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.T1A1SwitchCount, 20);
                deviceManager.hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T1A1, T1a1);
    
                // Scale Up
                float[] T1a2 = new float[9];
                T1a2[0] = scaleValue;
                T1a2[4] = scaleValue;
                T1a2[8] = 1f;
                deviceManager.hapticDevice.HapE.V3SendCommand(HapE.V3Command.T1A2SwitchCount, 20);
                deviceManager.hapticDevice.HapE.V3AnimatorSetTransform(HapE.V3AnimatorTransform.T1A2, T1a2);
                UpdateTrackerTransform();
                deviceManager.hapticDevice.HapE.Start();
            }
    
            // Test path tracing from x-y-t vectors
            public void SetDummyPath()
            {
                SetTracingPath(dummyPath);
            }
    
            /// <summary>
            /// Update Drawing Params
            /// </summary>
            /// <param name="value"></param>
            /// 
            public void SetBigA(float value)
            {
                bigA = value;
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigA, bigA);
            }
            public void SetBigB(float value)
            {
                bigB = value;
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.BigB, bigB);
            }
            public void SetA(float value)
            {
                a = value;
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.a, a);
            }
            public void SetB(float value)
            {
                b = value;
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.b, b);
            }
    
            public void SetDrawFrequnecy(float value)
            {
                drawFrequency = value;
                deviceManager.hapticDevice.HapE.V3PrimitiveSetParameter(HapE.V3PrimitiveParameter.DrawFrequency, drawFrequency);
            }
    
            public void UpdateTrackerTransform()
            {
                trackerTransform[0] = 1f;
                trackerTransform[3] = x;
                trackerTransform[5] = 1f;
                trackerTransform[7] = y;
                trackerTransform[10] = 1f;
                trackerTransform[11] = z;
                trackerTransform[15] = 1f;
                deviceManager.hapticDevice.HapE.V3SetTracker(trackerTransform);
            }
    
            public void TogglePlayback()
            {
                UpdateTrackerTransform();
                if (playing)
                {
                    deviceManager.hapticDevice.HapE.Stop();
                }
                else
                {
                    deviceManager.hapticDevice.HapE.Start();
                }
                playing = !playing;
            }
    
            // Update is called once per frame
            void Update()
            {
                if (Input.GetKeyUp("space"))
                {
                    TogglePlayback();
                }
    
            }
        }
    }

Aで横幅、Bで縦幅、DrawFrequencyで描画順(?)(線上で感覚を動かす感じ)を変えれました。

今後試したいこと

●モーションデザイナーで作ったモーションをエクスポートしてシーンにインポートしたい。

これはデモシーン(JSONPlayer)を使えば実証は簡単そう。

●つかんだら~などの条件を追加

LeapMotionDesigner

モーションを作ることができるソフト。

操作説明


全体の説明

haptocsdesighner.jpg

エディタ/モーションの大きさなどの編集画面

スクリーンショット 2024-11-15 170912.png

●BigA 円の横幅

●BigB 円の縦幅

●a 円を構成するためのsin cosどちらかの値だと思います。

●b 同上

●k 円を構成するためのtanの値っぽいです。

(a==b && k==1だと完全な円が描けます。)

●d y軸上に円を回転させられます。

●maxT 円の大きさを変えられます。

エディタ/点ごとのスケールの編集

usuorennjinotoko.jpg

エディタ/点ごとの出力の強さの編集

orenjinotoko.jpg

エディタ/ライブラリの使用

aoitoko.jpg

点の追加 手のマークがあるエディタ上で追加したい位置をクリック。

点の削除 消したい点にカーソルを合わせ、右クリック。

点の移動 移動させたい点にカーソルを合わせ、四角く枠が表示された状態でドラッグ。


Author: 松崎 | Source: 松崎\LeapMotionHaptics e3b72ba9c2184bb8a29bfb90961baaab.md