Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Returning Game Object to Original Position Upon Release from Oculus Touch Controller

Discussion in 'Scripting' started by hellmanagency, Sep 8, 2017.

  1. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    I have a machine setup with multiple child components. The machine sits on a pedestal that the player has the ability to rotate with a controller button (the machine then rotates with the pedestal). I am using the OVR Grab and OVR Grabbable sciprts (for the Oculus Rift touch controller) to allow the player to pull away components of the machine. I want to set it up so that when the player releases the object, it snaps back to it's original position and rotation relative to the machine (preferably with some ease and maybe some spring). I've tried using joints and haven't had any luck.

    I'm very new to Unity, so pretend like your explaining it to a 5 year old :)
     
  2. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    One way would be to give each part a small script to save it's initial local position and rotation. When you release a part, call a function or have the part listen for the "released" event or whatever (somehow tell the part it was dropped), and have the part move and rotate itself back to the initial local transform.

    You could also take the same approach but give the parent machine the script, and have it keep track of all the parts.

    I personally use DOTween for all my programmatic animations, because it's dope af.
     
  3. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    Yea I was thinking that I would probably have to setup some sort of script. Can you give me an idea of what that script might look like?
     
  4. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    The part could be as simple as this:
    Code (CSharp):
    1. using DG.Tweening; // this only works with the free DOTween asset
    2. using UnityEngine;
    3.  
    4. public class Part : MonoBehaviour
    5. {
    6.     public float moveSpeed;
    7.     public Ease moveEase;
    8.     public float rotateSpeed;
    9.     public Ease rotateEase;
    10.  
    11.     private Vector3 initialLocalPosition;
    12.     private Vector3 initialLocalRotation;
    13.  
    14.     // Unity function called once when the game starts
    15.     private void Start()
    16.     {
    17.         initialLocalPosition = transform.localPosition;
    18.         initialLocalRotation = transform.localRotation.eulerAngles;
    19.     }
    20.  
    21.     public void OnReleased()
    22.     {
    23.         // starts a tween with an ease, speed based as opposed to duration based
    24.         transform.DOLocalMove(initialLocalPosition, moveSpeed)
    25.             .SetSpeedBased()
    26.             .SetEase(moveEase);
    27.  
    28.         transform.DOLocalRotate(initialLocalRotation, rotateSpeed)
    29.             .SetSpeedBased()
    30.             .SetEase(rotateEase);
    31.     }
    32.  
    33.     public void OnGrabbed()
    34.     {
    35.         // stops all tweens on this transform
    36.         transform.DOKill();
    37.     }
    38. }
    Then it's just a matter of calling the OnGrabbed and OnReleased functions. I don't know how the grab/release script works, but if you can get it to call those functions (or does it throw events or something?) then this can work.
     
    Last edited: Sep 8, 2017
  5. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
  6. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    To actually help you alter the scripts I would need to see the source. If you paid for that, you can't share it here. You'll be working with the "GrabEnd" and "GrabBegin" functions.

    So assuming the script doesn't already have events to hook into, you can add some.

    So you can modify the provided Grabbable script like this:
    Code (CSharp):
    1. using UnityEngine.Events; // add this
    2.  
    3. // the provided oculus script
    4. public class Grabbable : MonoBehaviour
    5. {
    6.     // add these
    7.     public UnityEvent OnGrabBegin;
    8.     public UnityEvent OnGrabEnd;
    9.  
    10.     // pre-existing function
    11.     void GrabBegin()
    12.     {
    13.         // add this line
    14.         OnGrabBegin.Invoke();
    15.     }
    16.  
    17.     // pre-existing function
    18.     void GrabEnd()
    19.     {
    20.         // add this line
    21.         OnGrabEnd.Invoke();
    22.     }
    23. }
    Then your Part script can look like this:
    Code (CSharp):
    1. using DG.Tweening; // this only works with the free DOTween asset
    2. using UnityEngine;
    3.  
    4. public class Part : MonoBehaviour
    5. {
    6.     public float moveSpeed;
    7.     public Ease moveEase;
    8.     public float rotateSpeed;
    9.     public Ease rotateEase;
    10.  
    11.     private Vector3 initialLocalPosition;
    12.     private Vector3 initialLocalRotation;
    13.     private OVRGrabbable grabbable;
    14.  
    15.     private void Awake()
    16.     {
    17.         // get the grabbable component on this object
    18.         grabbable = GetComponent<OVRGrabbable>();
    19.     }
    20.  
    21.     // Unity function called once when the game starts
    22.     private void Start()
    23.     {
    24.         initialLocalPosition = transform.localPosition;
    25.         initialLocalRotation = transform.localRotation.eulerAngles;
    26.     }
    27.  
    28.     private void OnEnable()
    29.     {
    30.         // listen for grabs
    31.         grabbable.OnGrabBegin.AddListener(OnGrabbed);
    32.         grabbable.OnGrabEnd.AddListener(OnReleased);
    33.     }
    34.  
    35.     private void OnDisable()
    36.     {
    37.         // stop listening for grabs
    38.         grabbable.OnGrabBegin.RemoveListener(OnGrabbed);
    39.         grabbable.OnGrabEnd.RemoveListener(OnReleased);
    40.     }
    41.  
    42.     public void OnReleased()
    43.     {
    44.         // starts a tween with an ease, speed based as opposed to duration based
    45.         transform.DOLocalMove(initialLocalPosition, moveSpeed)
    46.             .SetSpeedBased()
    47.             .SetEase(moveEase);
    48.  
    49.         transform.DOLocalRotate(initialLocalRotation, rotateSpeed)
    50.             .SetSpeedBased()
    51.             .SetEase(rotateEase);
    52.     }
    53.  
    54.     public void OnGrabbed()
    55.     {
    56.         // stops all tweens on this transform
    57.         transform.DOKill();
    58.     }
    59. }
     
  7. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
  8. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    Luckily whoever wrote that was nice enough to make that class easily extensible.

    You can create a new script called "OVRGrabbableExtended", and paste this in:
    Code (CSharp):
    1. using UnityEngine.Events;
    2.  
    3. public class OVRGrabbableExtended : OVRGrabbable
    4. {
    5.     [HideInInspector] public UnityEvent OnGrabBegin;
    6.     [HideInInspector] public UnityEvent OnGrabEnd;
    7.  
    8.     public override void GrabBegin(OVRGrabber hand, Collider grabPoint)
    9.     {
    10.         OnGrabBegin.Invoke();
    11.         base.GrabBegin(hand, grabPoint);
    12.     }
    13.  
    14.     public override void GrabEnd(Vector3 linearVelocity, Vector3 angularVelocity)
    15.     {
    16.         base.GrabEnd(linearVelocity, angularVelocity);
    17.         OnGrabEnd.Invoke();
    18.     }
    19. }

    It will look and behave identically to the original one, except this one exposes some public events to hook into.

    So then the Part class would look like this (just changed the name of the grabbable class variable):
    Code (CSharp):
    1. using DG.Tweening; // this only works with the free DOTween asset
    2. using UnityEngine;
    3.  
    4. public class Part : MonoBehaviour {
    5.     public float moveSpeed;
    6.     public Ease moveEase;
    7.     public float rotateSpeed;
    8.     public Ease rotateEase;
    9.  
    10.     private Vector3 initialLocalPosition;
    11.     private Vector3 initialLocalRotation;
    12.     private OVRGrabbableExtended grabbable;
    13.  
    14.     private void Awake() {
    15.         // get the grabbable component on this object
    16.         grabbable = GetComponent<OVRGrabbable>();
    17.     }
    18.  
    19.     // Unity function called once when the game starts
    20.     private void Start() {
    21.         initialLocalPosition = transform.localPosition;
    22.         initialLocalRotation = transform.localRotation.eulerAngles;
    23.     }
    24.  
    25.     private void OnEnable() {
    26.         // listen for grabs
    27.         grabbable.OnGrabBegin.AddListener(OnGrabbed);
    28.         grabbable.OnGrabEnd.AddListener(OnReleased);
    29.     }
    30.  
    31.     private void OnDisable() {
    32.         // stop listening for grabs
    33.         grabbable.OnGrabBegin.RemoveListener(OnGrabbed);
    34.         grabbable.OnGrabEnd.RemoveListener(OnReleased);
    35.     }
    36.  
    37.     public void OnReleased() {
    38.         // starts a tween with an ease, speed based as opposed to duration based
    39.         transform.DOLocalMove(initialLocalPosition, moveSpeed)
    40.             .SetSpeedBased()
    41.             .SetEase(moveEase);
    42.  
    43.         transform.DOLocalRotate(initialLocalRotation, rotateSpeed)
    44.             .SetSpeedBased()
    45.             .SetEase(rotateEase);
    46.     }
    47.  
    48.     public void OnGrabbed() {
    49.         // stops all tweens on this transform
    50.         transform.DOKill();
    51.     }
    52. }

    I hope you will take the time to read through this and really understand what is going on so that you can start coding stuff like this for yourself moving forward. If you have any questions about any part of it, please ask.

    Also please note that I didn't test any of this, so hopefully it works, but if not I'll help you fix it.
     
  9. Scabbage

    Scabbage

    Joined:
    Dec 11, 2014
    Posts:
    268
    Honestly most of the time I don't bother with their sample scripts. It's usually better to do what you need using OVRInput directly.

    Oculus are nice about their SDK though, that's for sure. Pretty much all of the source is available, save for some external c++ libraries.
     
    LiterallyJeff likes this.
  10. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    I'm new to C#, so I'm slowly learning the language but it's probably going to take a while. I think I'm at least starting to grasp some of the basics.

    Unity is throwing an error on the OVRGrabbableExtended.cs script. It says "OVRGrabbableExtended.cs(8,53): error CS0246: The type or namespace name 'Collider' could not be found. Are you missing 'UnityEngine' using directive?". Any ideas?
     
  11. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    Add "using UnityEngine;" to the top.
     
  12. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    Easy for you to say :) Took me forever just to get things working with the pre-made scripts.
     
  13. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    Ok that script seems to be working now. However, now I'm getting an error on the Part.cs script. "Part.cs(18,21): error CS0266: Cannot implicitly convert type 'OVRGrabbable' to 'OVRGrabbableExtended'. An explicit conversion exists (are you missing a cast?)"
     
  14. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    woops, in the Awake function, change OVRGrabbable to OVRGrabbableExtended.
     
  15. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    Ok I think we are getting pretty close. I applied both scripts to my object that i want to grab, and I can pull it away from the machine. When I release it, the object moves to a position, but not the original one (it's somewhere up in the air).

    I also noticed that once I remove it and it goes back to its original place, it no longer rotates with the machine on the pedestal. I have the machine and pedestal as children of an empty game object. My rotation script is on the empty game object.
     
  16. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    So are the individual parts children of the machine, and do they stay children throughout the grab/release?

    My guess is that the part gets unparented, and then 'local' position becomes worldspace.
     
  17. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    Yes, the parts are children of the machine in the hierarchy. Right now I am focusing on removing a lid which is attached to the case. I have the hierarchy setup as Machine - Case - Lid. I applied a RigidBody tag applied to the lid with gravity disabled.
     
  18. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    Lets try saving the original parent transform, and restoring it when the object is dropped.
    Code (CSharp):
    1. using DG.Tweening; // this only works with the free DOTween asset
    2. using UnityEngine;
    3.  
    4. public class Part : MonoBehaviour
    5. {
    6.     public float moveSpeed;
    7.     public Ease moveEase;
    8.     public float rotateSpeed;
    9.     public Ease rotateEase;
    10.  
    11.     private Vector3 initialLocalPosition;
    12.     private Vector3 initialLocalRotation;
    13.     private Transform initialParent;
    14.     private OVRGrabbableExtended grabbable;
    15.  
    16.     private void Awake()
    17.     {
    18.         // get the grabbable component on this object
    19.         grabbable = GetComponent<OVRGrabbableExtended>();
    20.     }
    21.  
    22.     // Unity function called once when the game starts
    23.     private void Start()
    24.     {
    25.         initialLocalPosition = transform.localPosition;
    26.         initialLocalRotation = transform.localRotation.eulerAngles;
    27.         initialParent = transform.parent;
    28.     }
    29.  
    30.     private void OnEnable()
    31.     {
    32.         // listen for grabs
    33.         grabbable.OnGrabBegin.AddListener(OnGrabbed);
    34.         grabbable.OnGrabEnd.AddListener(OnReleased);
    35.     }
    36.  
    37.     private void OnDisable()
    38.     {
    39.         // stop listening for grabs
    40.         grabbable.OnGrabBegin.RemoveListener(OnGrabbed);
    41.         grabbable.OnGrabEnd.RemoveListener(OnReleased);
    42.     }
    43.  
    44.     private void OnReleased()
    45.     {
    46.         // restore parent
    47.         transform.SetParent(initialParent);
    48.  
    49.         // starts a tween with an ease, speed based as opposed to duration based
    50.         transform.DOLocalMove(initialLocalPosition, moveSpeed)
    51.             .SetSpeedBased()
    52.             .SetEase(moveEase);
    53.  
    54.         transform.DOLocalRotate(initialLocalRotation, rotateSpeed)
    55.             .SetSpeedBased()
    56.             .SetEase(rotateEase);
    57.     }
    58.  
    59.     private void OnGrabbed()
    60.     {
    61.         // stops all tweens on this transform
    62.         transform.DOKill();
    63.     }
    64. }
     
  19. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    The lid still floats off into space once I release it. I tried setting up a simplified version of my scene using primitive 3D shapes (without any rotation), and it does the same thing with the scripts applied.
     
    Last edited: Sep 8, 2017
  20. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    Take a look at the Lid's position before grabbing. Once you grab it and release it, check what it's new position is, and if it has a parent.
     
  21. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    After I release the lid, it just slowly floats off into space. I watched it for a few minutes and as far as I can tell it never stops. As for whether or not it's still has a parent, I'm not sure. I have it setup as a child in the hierarchy, so I would assume that they would maintain the parent/child relationship.
     
  22. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    Does each part have a rigidbody? Is it kinematic after dropping it? Sounds like it could be a physics issue perhaps. Try commenting out the "transform.DOLocalMove" line and see what happens. Try to isolate which bit might be the issue.

    The reason i ask about the parenting is because it's possible the oculus code does something with that. Generally speaking physics objects are usually not children, so you have to be sure and manage the kinematic state to keep control over it.
     
  23. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    I only have a RigidBody tag on the lid (with gravity & kinematics off). I tried commenting out that section of code and when I release the lid it stays stationary.

    When I enable "is Kinematic" for the lid, then the lid floats off to a set point and locks into place (which is what I want; it's just not the right point). When I rotate that platform, the lid stays where it is at and doesn't follow the case anymore. It DOES follow the case prior to when I grab it.
     
  24. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    So it looks like on the Grabber component there's a checkbox for "Parent Held Object". If that is set to true, that is very likely overriding whatever parenting you set up in the hierarchy, and setting it to null on release.

    You'll want to keep your objects kinematic as well, because it doesn't sound like you care about physics simulation during the grab/release.

    This setup is intended for physical grab+throw mechanics by default I believe.
     
    hellmanagency likes this.
  25. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    Have I mentioned that you are a genius? Wow this is amazing! So I disabled that "Parent Held Object" checkbox and enabled kinematics for the lid. Now when I release, it moves to the right spot AND follows the machine when it rotates.

    Now the only thing that isn't working properly is the rotation. It appears to be staying at whatever the rotation value was when I release it.
     
  26. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    Thank you for the kind words lol

    Well first verify the rotationSpeed is not 0 or too small to notice.

    Then try outputting some values to see whats going on.

    In the OnRelease function, at the top before the tween starts, add this line:
    Code (CSharp):
    1. Debug.LogFormat("InitialLocal: {0} | CurrentLocal: {1} | CurrentWorld: {2}", initialLocalRotation, transform.localRotation, transform.rotation);
    and let me know what that outputs in the console in unity when you drop a part.
     
  27. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    Ok I tried putting the rotationSpeed up higher, and now I do see that eventually it does return to the correct rotation value. However, it seems like it's waiting for a few seconds before it goes. When I use the same value for the Move Speed it returns right away. Here is the debug info.

    InitialLocal: (270.0, 0.0, 0.0) | CurrentLocal: (-0.4, -0.6, 0.2, 0.6) | CurrentWorld: (-0.1, 0.0, 0.4, 0.9)
    UnityEngine.Debug:LogFormat(String, Object[])
    Part:OnReleased() (at Assets/OVR/Scripts/Util/Part.cs:46)
    UnityEngine.Events.UnityEvent:Invoke()
    OVRGrabbableExtended:GrabEnd(Vector3, Vector3) (at Assets/OVR/Scripts/Util/OVRGrabbableExtended.cs:18)
    OVRGrabber:GrabbableRelease(Vector3, Vector3) (at Assets/OVR/Scripts/Util/OVRGrabber.cs:347)
    OVRGrabber:GrabEnd() (at Assets/OVR/Scripts/Util/OVRGrabber.cs:338)
    OVRGrabber:CheckForGrabOrRelease(Single) (at Assets/OVR/Scripts/Util/OVRGrabber.cs:210)
    OVRGrabber:OnUpdatedAnchors() (at Assets/OVR/Scripts/Util/OVRGrabber.cs:156)
    OVRGrabber:FixedUpdate() (at Assets/OVR/Scripts/Util/OVRGrabber.cs:130)
     
  28. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    You don't need to make the two speeds the same value, they represent different units. Feel free to crank up the rotation speed until it looks right.

    Since it is in fact rotating you don't have to worry about the debug stuff.

    So you're certain there's a delay?
     
  29. hellmanagency

    hellmanagency

    Joined:
    Aug 29, 2017
    Posts:
    16
    Ok I played around with the values a bit and I think it's working fine now. I think it was a combination of the slower speed with the elastic effect that made it seem like there was a delay. Anyway, I think I'm all set now. Thanks you so much for all of your help!
     
    LiterallyJeff likes this.
  30. V-J

    V-J

    Joined:
    Apr 1, 2015
    Posts:
    73
  31. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    You could use a coroutine to interpolate the position and rotation over time in a loop.
     
  32. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    Are they floating away?
    That sounds like this issue from earlier in the thread: https://forum.unity.com/threads/ret...-oculus-touch-controller.494387/#post-3213808

    Make sure your rigidbody objects are kinematic so you can fully control them with the animations.

    If they're just staying in place, then try to debug your OnRelease function to make sure it's getting called.
     
  33. V-J

    V-J

    Joined:
    Apr 1, 2015
    Posts:
    73
    Somehow the OnRelease function is not getting called with the extended script, i dont exactly know why, so i have put the OnGrabBegin.Invoke() and OnGrabEnd.Invoke() in the Oculus OVRGrabbable script that comes with the intergration package as you refer earlier in the thread. Now it works! thx for spreading your knowledge in this post!

    In addition to that, i like to add an if inrange function, so when the grabbed gameobject is in a certain range and released it snaps back to the original position, when the object is released out of range it must behave like normal (gravity on).

    For the first part, i used a if statement, but the result is that it only works good with individual parts. When the grabbed gameobjects are parented they stay at the position with no movement or rotation where they are released. both inrange and outrange and gravity is off.

    Code (CSharp):
    1. public void OnReleased()
    2.     {
    3.         //RestorePosition();
    4.  
    5.         float dist = Vector3.Distance(transform.position, initialLocalPosition);
    6.  
    7.         if (dist < 0.5f)
    8.         {
    9.             RestorePosition();
    10.         }
    11.         else
    12.         {
    13.             transform.parent = null;
    14.             //rb.useGravity = true;
    15.         }
    16.  
    17.     }
     
    Last edited: Sep 4, 2019
  34. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    float dist = Vector3.Distance(transform.position, initialLocalPosition);

    This distance function isn't valid here, because you're comparing a world position to a local position in the initial parent's space.

    I think the easiest way is to create a variable to store the initial world position as well, and use that for this comparison.

    However if you're rotating the original parent or something during the grab and want to take that change of position into consideration, you can convert the local position to world position using the parent transform.

    Vector3 initialWorldPosition = initialParent.TransformPoint(initialLocalPosition);
     
    Last edited: Sep 6, 2019
  35. V-J

    V-J

    Joined:
    Apr 1, 2015
    Posts:
    73
    Thank you so much for your help! its working now.
     
    LiterallyJeff likes this.
  36. nextage575

    nextage575

    Joined:
    Nov 4, 2019
    Posts:
    20
    i am moving gameObject like this

    1. float moveHorizontal = CrossPlatformInputManager.GetAxis("Horizontal");
    2. float moveVertical = CrossPlatformInputManager.GetAxis("Vertical");
    3. Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
    4. transform.rotation = Quaternion.LookRotation(movement);
    5. transform.Translate(movement * Time.deltaTime * carSpeed, Space.World);
    how to fix this same issue it reset when i release joystick.
     
  37. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    Please don't necro post to old unrelated threads. Instead, start your own post. It's FREE!

    When you post, remember that nobody knows what you are talking about, and that likely all the people above are long gone.

    This means YOU have to communicate. When you make your new fresh post, here is how to report your problem productively in the Unity3D forums:

    http://plbm.com/?p=220

    This is a bare minimum of information to report:

    - what you want
    - what you tried
    - what you expected to happen
    - what actually happened, especially any errors you see

    Also, remember that photographs of code are NOT A THING.

    If you post a code snippet, ALWAYS USE CODE TAGS:

    How to use code tags: https://forum.unity.com/threads/using-code-tags-properly.143875/
     
    Jessie9999 likes this.