Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  Cloth to simulate slime
#11
(10-09-2018, 04:03 PM)arrnav96 Wrote: Just tried doing what you mentioned. Made no difference. Sphere sinks straight through it. Even went up to 4 substeps and 8 distance contraint iterations. Same result. May I send you the project so you can have a look? Nothing I try seems to make a difference here.

Here:



The video starts with the basic cloth setup: Create a GameObject with cloth & solver components, choose a cloth topology (I have multiple ready-made ones, so I skipped topology generation) and hit "Initialize".

Then I fix the four corners of the cloth (similar to the setup you describe), and create a 1 kg ball on top of it.
Click play, and the ball sinks trough the cloth. ([1:18] in the video)

Note that I left the cloth mass at its default value of 0.1 kg per particle, for the sake of demonstrating iterations/substeps only. First I try 5 iterations / 2 substeps. Since the cloth mesh is quite dense -made out of many particles- and light, the ball still slips trough. Then I try 5 iterations / 4 substeps, and now cloth is able to withstand the weight of the ball, despite having a mass ratio of 1/10. Reducing the mass ratio by increasing the weight of the cloth would reduce the need for high iteration/substep counts, reducing the density of the mesh would too.

After that, I add a particle renderer component so that you can see the cloth particles. Then begin to reduce the amount of substeps again, until the ball is able to rip a gap between the particles and slip trough.

Hope the video helps you.

PS: I have a feeling that your original goal got lost during this conversation... assuming your slime sim is 2D, I think it would be both easier and much more performant to simply move particles around directly (those near the user's finger), using Oni.SetParticleVelocities()?
Reply
#12
(10-09-2018, 04:27 PM)josemendez Wrote: Here:



The video starts with the basic cloth setup: Create a GameObject with cloth & solver components, choose a cloth topology (I have multiple ready-made ones, so I skipped topology generation) and hit "Initialize".

Then I fix the four corners of the cloth (similar to the setup you describe), and create a 1 kg ball on top of it.
Click play, and the ball sinks trough the cloth. ([1:18] in the video)

Note that I left the cloth mass at its default value of 0.1 kg per particle, for the sake of demonstrating iterations/substeps only. First I try 5 iterations / 2 substeps. Since the cloth mesh is quite dense -made out of many particles- and light, the ball still slips trough. Then I try 5 iterations / 4 substeps, and now cloth is able to withstand the weight of the ball, despite having a mass ratio of 1/10. Reducing the mass ratio by increasing the weight of the cloth would reduce the need for high iteration/substep counts, reducing the density of the mesh would too.

After that, I add a particle renderer component so that you can see the cloth particles. Then begin to reduce the amount of substeps again, until the ball is able to rip a gap between the particles and slip trough.

Hope the video helps you.

PS: I have a feeling that your original goal got lost during this conversation... assuming your slime sim is 2D, I think it would be both easier and much more performant to simply move particles around directly (those near the user's finger), using Oni.SetParticleVelocities()?

Thankyou. Your support is astonishingly helpful. I'm going to try that setup tonight as soon as I get home.

That being said, originally, as you mentioned in the end, I wanted to move particles around the user's finger using SetParticleVelocities(). But I'm unable to understand how would I be able to create a finger's shape on slime's surface. 

If I attenuate positions of particles around the user's finger using a slope equation, I don't understand how that would recreate a "finger shape".

Is increasing substeps really inefficient for such use cases?

I feel the only thing stopping me from using SetParticleVelocities() is the finger shape on surface. 

Also, do you have suggestions on how to make the cloth more "Slimy"? I tried altering values such as stiffness but can't quite get it right.
Reply
#13
(11-09-2018, 09:25 AM)arrnav96 Wrote: That being said, originally, as you mentioned in the end, I wanted to move particles around the user's finger using SetParticleVelocities(). But I'm unable to understand how would I be able to create a finger's shape on slime's surface. 

If I attenuate positions of particles around the user's finger using a slope equation, I don't understand how that would recreate a "finger shape".

A finger is basically a circular (or ellipsoidal) shape. Using a radial support function you can displace particles to the border of the circle/ellipsoid, to make it look like the user is "pressing down" on the slime.

Keep in mind that while the user is dragging, it is very unlikely he will see what's happening under his finger. Something I'd think about would be splatting a fingerprint normalmap on top of the slime, and vanish it over time (a few seconds). This would allow the user to see his "fingerprints" for a while after raising the finger off the screen.

(11-09-2018, 09:25 AM)arrnav96 Wrote: Is increasing substeps really inefficient for such use cases?

Using more substeps basically means re-doing the entire simulation more times per frame, including collision detection. So yes, if all you're looking for is relatively simple user interaction, substepping is complete overkill (specially for mobile)

(11-09-2018, 09:25 AM)arrnav96 Wrote: Also, do you have suggestions on how to make the cloth more "Slimy"? I tried altering values such as stiffness but can't quite get it right.

Looking slimy, or behaving slimy?
If the first, use a shader with high smoothness/glossiness and a normal map.
If the second, increase distance constraints slack and decrease stiffness a bit. Also consider increasing the solver's "damping" for a thicker/viscous behavior.
Reply
#14
(11-09-2018, 10:32 AM)josemendez Wrote: A finger is basically a circular (or ellipsoidal) shape. Using a radial support function you can displace particles to the border of the circle/ellipsoid, to make it look like the user is "pressing down" on the slime.

Keep in mind that while the user is dragging, it is very unlikely he will see what's happening under his finger. Something I'd think about would be splatting a fingerprint normalmap on top of the slime, and vanish it over time (a few seconds). This would allow the user to see his "fingerprints" for a while after raising the finger off the screen.


Using more substeps basically means re-doing the entire simulation more times per frame, including collision detection. So yes, if all you're looking for is relatively simple user interaction, substepping is complete overkill (specially for mobile)


Looking slimy, or behaving slimy?
If the first, use a shader with high smoothness/glossiness and a normal map.
If the second, increase distance constraints slack and decrease stiffness a bit. Also consider increasing the solver's "damping" for a thicker/viscous behavior.

I notice you have a ObiClothDragger.cs in the asset. It appears to be an appropriate script close to the purpose. So I tried applying this script to the slime object, with the y-position vector force reversed in script, to show a "press" instead of a drag.

This is the result.

It appears to push the ground zero particle quite deep so a proper finger press effect appears.

But the mouse position does not translate correctly onto the screen. It seems that it does not respect the mouse's world positions.

Also, why does the script use vector4?
Reply
#15
(11-09-2018, 10:25 PM)arrnav96 Wrote: I notice you have a ObiClothDragger.cs in the asset. It appears to be an appropriate script close to the purpose. So I tried applying this script to the slime object, with the y-position vector force reversed in script, to show a "press" instead of a drag.

This is the result.

It appears to push the ground zero particle quite deep so a proper finger press effect appears.

But the mouse position does not translate correctly onto the screen. It seems that it does not respect the mouse's world positions.

Also, why does the script use vector4?

HI,

I can't reproduce that behavior, tried the same in my computer and it works fine. Can you share your setup options? also, what mesh are you using for the cloth, is it a custom one?

Vector4 (instead of Vector3) is used throughout Obi to take advantage of SIMD. This gives a roughly x4 speedup for all operations involving positions/velocities. The reason is that SSE/NEON/AVX intrinsics can only be used on 16-byte aligned data, and work on a number of operands that is power of 2. Also, the extra operand is used internally to store extra data, and since converting from Vector4 to Vector3 just for the API is a lot of overhead, we simply decided to expose Vector4s. See:
https://en.wikipedia.org/wiki/SIMD
Reply
#16
(12-09-2018, 08:09 AM)josemendez Wrote: HI,

I can't reproduce that behavior, tried the same in my computer and it works fine. Can you share your setup options? also, what mesh are you using for the cloth, is it a custom one?

Vector4 (instead of Vector3) is used throughout Obi to take advantage of SIMD. This gives a roughly x4 speedup for all operations involving positions/velocities. The reason is that SSE/NEON/AVX intrinsics can only be used on 16-byte aligned data, and work on a number of operands that is power of 2. Also, the extra operand is used internally to store extra data, and since converting from Vector4 to Vector3 just for the API is a lot of overhead, we simply decided to expose Vector4s. See:
https://en.wikipedia.org/wiki/SIMD

I've solved the issue.

Is there any way of adapting this for multiple fingers? I was also trying the sphere method so I could use multiple fingers.

Does the cloth dragger's GetMouseButtonDown(0) work with multiple touch points?
Reply
#17
(12-09-2018, 01:01 PM)arrnav96 Wrote: I've solved the issue.

Is there any way of adapting this for multiple fingers? I was also trying the sphere method so I could use multiple fingers.

Does the cloth dragger's GetMouseButtonDown(0) work with multiple touch points?

Use Input.GetTouch() instead, passing the index of the touch:

https://unity3d.com/es/learn/tutorials/t...ouch-input
Reply
#18
(12-09-2018, 02:26 PM)josemendez Wrote: Use Input.GetTouch() instead, passing the index of the touch:

https://unity3d.com/es/learn/tutorials/t...ouch-input

So if I use GetTouch, the dragging works, but it's glitchy. Also, let's say I have a touch count of two (two fingers on screen). On touching with both fingers, the slime actually gets pressed in the center of the distance between the two finger's positions. It's as if the code is averaging out the pressed point's location between all finger locations - resulting in only one "press" on screen. I know I'm going off topic, but I'm lost with multitouch right now.

Also, the touch itself is very glitchy. The "pressed" part constantly seems to vibrate while being dragged as shown in this video.

My guess is the script is getting confused between which particle to select according to ScreenToCamera point, amongst adjacent and close particles. I tried increasing the number of particles by setting up the SheetVHD mesh instead as a cloth, which has many more particles once initialized, but the vibration-on-touch effect is still visible.

These are my changes to the two scripts so far -

ObiClothPicker.cs -

Code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Obi {

   public class ObiClothPicker : MonoBehaviour {

       public class ParticlePickEventArgs : EventArgs {

           public int particleIndex;
           public Vector3 worldPosition;

           public ParticlePickEventArgs(int particleIndex, Vector3 worldPosition) {
               this.particleIndex = particleIndex;
               this.worldPosition = worldPosition;
           }
       }

       public event System.EventHandler<ParticlePickEventArgs> OnParticlePicked;
       public event System.EventHandler<ParticlePickEventArgs> OnParticleHeld;
       public event System.EventHandler<ParticlePickEventArgs> OnParticleDragged;
       public event System.EventHandler<ParticlePickEventArgs> OnParticleReleased;

       private MeshCollider meshCollider;
       private ObiClothBase cloth;
       private Mesh currentCollisionMesh;

       private Vector3 lastMousePos = Vector3.zero;
       private int pickedParticleIndex = -1;
       private float pickedParticleDepth = 0;

       public ObiClothBase Cloth {
           get { return cloth; }
       }

       void Awake() {
           cloth = GetComponent<ObiClothBase>();
           lastMousePos = Input.mousePosition;
       }

       void OnEnable() {

           // special case for skinned cloth, the collider must be added to the skeleton's root bone:
           if (cloth is ObiCloth && ((ObiCloth)cloth).IsSkinned) {

               SkinnedMeshRenderer sk = cloth.GetComponent<SkinnedMeshRenderer>();
               if (sk != null && sk.rootBone != null) {
                   meshCollider = sk.rootBone.gameObject.AddComponent<MeshCollider>();
               }
           }
           // regular cloth:
           else {
               meshCollider = gameObject.AddComponent<MeshCollider>();
           }

           // in case we were able to create the mesh collider, set it up:
           if (meshCollider != null) {
               meshCollider.enabled = false;
               meshCollider.hideFlags = HideFlags.HideAndDontSave;
           }

           if (cloth != null)
               cloth.Solver.OnFrameBegin += Cloth_Solver_OnFrameBegin;
       }

       void OnDisable() {

           // destroy the managed mesh collider:
           GameObject.Destroy(meshCollider);

           if (cloth != null)
               cloth.Solver.OnFrameBegin -= Cloth_Solver_OnFrameBegin;
       }

       void Cloth_Solver_OnFrameBegin(object sender, EventArgs e)
       {
           if (meshCollider == null)
               return;

           // Click:
#if UNITY_ANDROID
           Touch myTouch = Input.GetTouch(0);

           Touch[] myTouches = Input.touches;
           for (int i = 0; i < Input.touchCount; i++)
           {
               meshCollider.enabled = true;

               GameObject.Destroy(currentCollisionMesh);
               currentCollisionMesh = GameObject.Instantiate(cloth.clothMesh);
               meshCollider.sharedMesh = currentCollisionMesh;

               Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

               RaycastHit hitInfo;
               if (meshCollider.Raycast(ray, out hitInfo, 100))
               {

                   int[] tris = currentCollisionMesh.triangles;
                   Vector3[] vertices = currentCollisionMesh.vertices;

                   // find closest vertex in the triangle we just hit:
                   int closestVertex = -1;
                   float minDistance = float.MaxValue;

                   for (int j = 0; j < 3; ++j)
                   {
                       int vertex = tris[hitInfo.triangleIndex * 3 + j];
                       float distance = (vertices[vertex] - hitInfo.point).sqrMagnitude;
                       if (distance < minDistance)
                       {
                           minDistance = distance;
                           closestVertex = vertex;
                       }
                   }

                   // get particle index:
                   if (closestVertex >= 0 && closestVertex < cloth.topology.visualMap.Length)
                   {

                       pickedParticleIndex = cloth.topology.visualMap[closestVertex];
                       pickedParticleDepth = Mathf.Abs((cloth.transform.TransformPoint(vertices[closestVertex]) - Camera.main.transform.position).z);

                       if (OnParticlePicked != null)
                       {
                           Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
                           OnParticlePicked(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
                       }
                   }
               }

               meshCollider.enabled = false;
           }

           if (Input.touchCount < 1)
           {
               if (OnParticleReleased != null)
               {
                   Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
                   OnParticleReleased(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
               }

               pickedParticleIndex = -1;
           }
#endif

#if UNITY_EDITOR
           if (Input.GetMouseButton(0))
           {
               meshCollider.enabled = true;

               GameObject.Destroy(currentCollisionMesh);
               currentCollisionMesh = GameObject.Instantiate(cloth.clothMesh);
               meshCollider.sharedMesh = currentCollisionMesh;

               Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

               RaycastHit hitInfo;
               if (meshCollider.Raycast(ray, out hitInfo, 100))
               {

                   int[] tris = currentCollisionMesh.triangles;
                   Vector3[] vertices = currentCollisionMesh.vertices;

                   // find closest vertex in the triangle we just hit:
                   int closestVertex = -1;
                   float minDistance = float.MaxValue;

                   for (int j = 0; j < 3; ++j)
                   {
                       int vertex = tris[hitInfo.triangleIndex * 3 + j];
                       float distance = (vertices[vertex] - hitInfo.point).sqrMagnitude;
                       if (distance < minDistance)
                       {
                           minDistance = distance;
                           closestVertex = vertex;
                       }
                   }

                   // get particle index:
                   if (closestVertex >= 0 && closestVertex < cloth.topology.visualMap.Length)
                   {

                       pickedParticleIndex = cloth.topology.visualMap[closestVertex];
                       pickedParticleDepth = Mathf.Abs((cloth.transform.TransformPoint(vertices[closestVertex]) - Camera.main.transform.position).z);

                       if (OnParticlePicked != null)
                       {
                           Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
                           OnParticlePicked(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
                       }
                   }
               }

               meshCollider.enabled = false;
           }

           else if (pickedParticleIndex >= 0) {

               // Drag:
               /*Vector3 mouseDelta = Input.mousePosition - lastMousePos;
                if (mouseDelta.magnitude > 0.01f && OnParticleDragged != null){

                    Vector3 worldPosition = Camera.main.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));                    
                   OnParticleDragged(this,new ParticlePickEventArgs(pickedParticleIndex,worldPosition));

                }else if (OnParticleHeld != null){

                    Vector3 worldPosition = Camera.main.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
                    OnParticleHeld(this,new ParticlePickEventArgs(pickedParticleIndex,worldPosition));

                }*/

               // Release:                
               if (Input.GetMouseButtonUp(0)) {

                   if (OnParticleReleased != null) {
                       Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
                       OnParticleReleased(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
                   }

                   pickedParticleIndex = -1;
               }
           }

           lastMousePos = Input.mousePosition;
#endif
           
       }
   }
}

ObiClothDragger.cs -

Code:
using System;
using UnityEngine;

namespace Obi
{
    [RequireComponent(typeof(ObiClothPicker))]
    public class ObiClothDragger : MonoBehaviour
    {
        public float springStiffness = 50;
        public float springDamping = 1;

        private ObiClothPicker picker;
        private ObiClothPicker.ParticlePickEventArgs pickArgs;

        void OnEnable()
        {
            picker = GetComponent<ObiClothPicker>();
            picker.OnParticlePicked += Picker_OnParticleDragged;
            picker.OnParticleDragged += Picker_OnParticleDragged;
            picker.OnParticleReleased += Picker_OnParticleReleased;
        }

        void OnDisable()
        {
            picker.OnParticlePicked -= Picker_OnParticleDragged;
            picker.OnParticleDragged -= Picker_OnParticleDragged;
            picker.OnParticleReleased -= Picker_OnParticleReleased;
        }

        void FixedUpdate ()
        {
            if (pickArgs != null){
                
                ObiSolver solver = picker.Cloth.Solver;

                // Calculate picking position in solver space:
                Vector3 targetPosition = pickArgs.worldPosition;    
                if (solver.simulateInLocalSpace)
                    targetPosition = solver.transform.InverseTransformPoint(targetPosition);
    
                // Get particle position and velocity:
                Vector4[] positions = new Vector4[1];
                Vector4[] velocities = new Vector4[1];
                int solverIndex = picker.Cloth.particleIndices[pickArgs.particleIndex];
                Oni.GetParticlePositions(solver.OniSolver,positions,1,solverIndex);
                Oni.GetParticleVelocities(solver.OniSolver,velocities,1,solverIndex);

                // Calculate effective inverse mass:
                float invMass = picker.Cloth.invMasses[pickArgs.particleIndex] * picker.Cloth.areaContribution[pickArgs.particleIndex];

                if (invMass > 0){
                    // Calculate and apply spring force:
                   
                    Vector4 force = ((new Vector4(targetPosition[0],-targetPosition[1],targetPosition[2],0) - positions[0]) * springStiffness - velocities[0] * springDamping) / (invMass);                    
                    Oni.AddParticleExternalForce(picker.Cloth.Solver.OniSolver,ref force,new int[]{solverIndex},1);
                }
                
            }
        }

        void Picker_OnParticleDragged (object sender, ObiClothPicker.ParticlePickEventArgs e)
        {
            pickArgs = e;
        }

        void Picker_OnParticleReleased (object sender, ObiClothPicker.ParticlePickEventArgs e)
        {
            pickArgs = null;
        }

    }
}

Lastly, in ObiClothDragger.cs, in fixed update, while calculating and applying spring force, I tried changing the line -
Code:
Vector4 force = ((new Vector4(targetPosition[0],-targetPosition[1],targetPosition[2],0) - positions[0]) * springStiffness - velocities[0] * springDamping) / (invMass);

to this...

Code:
Vector4 force = ((new Vector4(targetPosition[0],-targetPosition[1]-10,targetPosition[2],0) - positions[0]) * springStiffness - velocities[0] * springDamping) / (invMass);

..so that I could provide an increased depth effect at the point of touch (instead of using a splat map, to get required "pressed" visual effect). But it makes the whole cloth glitch out and disappear instantly. How do I increase the depth of the "press"? For now I was assuming "targetPosition[1]" is the Y-Position of the press.
Reply
#19
(12-09-2018, 03:44 PM)arrnav96 Wrote: So if I use GetTouch, the dragging works, but it's glitchy. Also, let's say I have a touch count of two (two fingers on screen). On touching with both fingers, the slime actually gets pressed in the center of the distance between the two finger's positions. It's as if the code is averaging out the pressed point's location between all finger locations - resulting in only one "press" on screen. I know I'm going off topic, but I'm lost with multitouch right now.

Also, the touch itself is very glitchy. The "pressed" part constantly seems to vibrate while being dragged as shown in this video.

My guess is the script is getting confused between which particle to select according to ScreenToCamera point, amongst adjacent and close particles. I tried increasing the number of particles by setting up the SheetVHD mesh instead as a cloth, which has many more particles once initialized, but the vibration-on-touch effect is still visible.

These are my changes to the two scripts so far -

ObiClothPicker.cs -

Code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Obi {

   public class ObiClothPicker : MonoBehaviour {

       public class ParticlePickEventArgs : EventArgs {

           public int particleIndex;
           public Vector3 worldPosition;

           public ParticlePickEventArgs(int particleIndex, Vector3 worldPosition) {
               this.particleIndex = particleIndex;
               this.worldPosition = worldPosition;
           }
       }

       public event System.EventHandler<ParticlePickEventArgs> OnParticlePicked;
       public event System.EventHandler<ParticlePickEventArgs> OnParticleHeld;
       public event System.EventHandler<ParticlePickEventArgs> OnParticleDragged;
       public event System.EventHandler<ParticlePickEventArgs> OnParticleReleased;

       private MeshCollider meshCollider;
       private ObiClothBase cloth;
       private Mesh currentCollisionMesh;

       private Vector3 lastMousePos = Vector3.zero;
       private int pickedParticleIndex = -1;
       private float pickedParticleDepth = 0;

       public ObiClothBase Cloth {
           get { return cloth; }
       }

       void Awake() {
           cloth = GetComponent<ObiClothBase>();
           lastMousePos = Input.mousePosition;
       }

       void OnEnable() {

           // special case for skinned cloth, the collider must be added to the skeleton's root bone:
           if (cloth is ObiCloth && ((ObiCloth)cloth).IsSkinned) {

               SkinnedMeshRenderer sk = cloth.GetComponent<SkinnedMeshRenderer>();
               if (sk != null && sk.rootBone != null) {
                   meshCollider = sk.rootBone.gameObject.AddComponent<MeshCollider>();
               }
           }
           // regular cloth:
           else {
               meshCollider = gameObject.AddComponent<MeshCollider>();
           }

           // in case we were able to create the mesh collider, set it up:
           if (meshCollider != null) {
               meshCollider.enabled = false;
               meshCollider.hideFlags = HideFlags.HideAndDontSave;
           }

           if (cloth != null)
               cloth.Solver.OnFrameBegin += Cloth_Solver_OnFrameBegin;
       }

       void OnDisable() {

           // destroy the managed mesh collider:
           GameObject.Destroy(meshCollider);

           if (cloth != null)
               cloth.Solver.OnFrameBegin -= Cloth_Solver_OnFrameBegin;
       }

       void Cloth_Solver_OnFrameBegin(object sender, EventArgs e)
       {
           if (meshCollider == null)
               return;

           // Click:
#if UNITY_ANDROID
           Touch myTouch = Input.GetTouch(0);

           Touch[] myTouches = Input.touches;
           for (int i = 0; i < Input.touchCount; i++)
           {
               meshCollider.enabled = true;

               GameObject.Destroy(currentCollisionMesh);
               currentCollisionMesh = GameObject.Instantiate(cloth.clothMesh);
               meshCollider.sharedMesh = currentCollisionMesh;

               Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

               RaycastHit hitInfo;
               if (meshCollider.Raycast(ray, out hitInfo, 100))
               {

                   int[] tris = currentCollisionMesh.triangles;
                   Vector3[] vertices = currentCollisionMesh.vertices;

                   // find closest vertex in the triangle we just hit:
                   int closestVertex = -1;
                   float minDistance = float.MaxValue;

                   for (int j = 0; j < 3; ++j)
                   {
                       int vertex = tris[hitInfo.triangleIndex * 3 + j];
                       float distance = (vertices[vertex] - hitInfo.point).sqrMagnitude;
                       if (distance < minDistance)
                       {
                           minDistance = distance;
                           closestVertex = vertex;
                       }
                   }

                   // get particle index:
                   if (closestVertex >= 0 && closestVertex < cloth.topology.visualMap.Length)
                   {

                       pickedParticleIndex = cloth.topology.visualMap[closestVertex];
                       pickedParticleDepth = Mathf.Abs((cloth.transform.TransformPoint(vertices[closestVertex]) - Camera.main.transform.position).z);

                       if (OnParticlePicked != null)
                       {
                           Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
                           OnParticlePicked(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
                       }
                   }
               }

               meshCollider.enabled = false;
           }

           if (Input.touchCount < 1)
           {
               if (OnParticleReleased != null)
               {
                   Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
                   OnParticleReleased(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
               }

               pickedParticleIndex = -1;
           }
#endif

#if UNITY_EDITOR
           if (Input.GetMouseButton(0))
           {
               meshCollider.enabled = true;

               GameObject.Destroy(currentCollisionMesh);
               currentCollisionMesh = GameObject.Instantiate(cloth.clothMesh);
               meshCollider.sharedMesh = currentCollisionMesh;

               Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

               RaycastHit hitInfo;
               if (meshCollider.Raycast(ray, out hitInfo, 100))
               {

                   int[] tris = currentCollisionMesh.triangles;
                   Vector3[] vertices = currentCollisionMesh.vertices;

                   // find closest vertex in the triangle we just hit:
                   int closestVertex = -1;
                   float minDistance = float.MaxValue;

                   for (int j = 0; j < 3; ++j)
                   {
                       int vertex = tris[hitInfo.triangleIndex * 3 + j];
                       float distance = (vertices[vertex] - hitInfo.point).sqrMagnitude;
                       if (distance < minDistance)
                       {
                           minDistance = distance;
                           closestVertex = vertex;
                       }
                   }

                   // get particle index:
                   if (closestVertex >= 0 && closestVertex < cloth.topology.visualMap.Length)
                   {

                       pickedParticleIndex = cloth.topology.visualMap[closestVertex];
                       pickedParticleDepth = Mathf.Abs((cloth.transform.TransformPoint(vertices[closestVertex]) - Camera.main.transform.position).z);

                       if (OnParticlePicked != null)
                       {
                           Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
                           OnParticlePicked(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
                       }
                   }
               }

               meshCollider.enabled = false;
           }

           else if (pickedParticleIndex >= 0) {

               // Drag:
               /*Vector3 mouseDelta = Input.mousePosition - lastMousePos;
if (mouseDelta.magnitude > 0.01f && OnParticleDragged != null){

Vector3 worldPosition = Camera.main.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));                    
                   OnParticleDragged(this,new ParticlePickEventArgs(pickedParticleIndex,worldPosition));

}else if (OnParticleHeld != null){

Vector3 worldPosition = Camera.main.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
OnParticleHeld(this,new ParticlePickEventArgs(pickedParticleIndex,worldPosition));

}*/

               // Release:
               if (Input.GetMouseButtonUp(0)) {

                   if (OnParticleReleased != null) {
                       Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
                       OnParticleReleased(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
                   }

                   pickedParticleIndex = -1;
               }
           }

           lastMousePos = Input.mousePosition;
#endif
           
       }
   }
}

ObiClothDragger.cs -

Code:
using System;
using UnityEngine;

namespace Obi
{
[RequireComponent(typeof(ObiClothPicker))]
public class ObiClothDragger : MonoBehaviour
{
public float springStiffness = 50;
public float springDamping = 1;

private ObiClothPicker picker;
private ObiClothPicker.ParticlePickEventArgs pickArgs;

void OnEnable()
{
picker = GetComponent<ObiClothPicker>();
picker.OnParticlePicked += Picker_OnParticleDragged;
picker.OnParticleDragged += Picker_OnParticleDragged;
picker.OnParticleReleased += Picker_OnParticleReleased;
}

void OnDisable()
{
picker.OnParticlePicked -= Picker_OnParticleDragged;
picker.OnParticleDragged -= Picker_OnParticleDragged;
picker.OnParticleReleased -= Picker_OnParticleReleased;
}

void FixedUpdate ()
{
if (pickArgs != null){

ObiSolver solver = picker.Cloth.Solver;

// Calculate picking position in solver space:
Vector3 targetPosition = pickArgs.worldPosition;
if (solver.simulateInLocalSpace)
targetPosition = solver.transform.InverseTransformPoint(targetPosition);

// Get particle position and velocity:
Vector4[] positions = new Vector4[1];
Vector4[] velocities = new Vector4[1];
int solverIndex = picker.Cloth.particleIndices[pickArgs.particleIndex];
Oni.GetParticlePositions(solver.OniSolver,positions,1,solverIndex);
Oni.GetParticleVelocities(solver.OniSolver,velocities,1,solverIndex);

// Calculate effective inverse mass:
float invMass = picker.Cloth.invMasses[pickArgs.particleIndex] * picker.Cloth.areaContribution[pickArgs.particleIndex];

if (invMass > 0){
// Calculate and apply spring force:
                   
Vector4 force = ((new Vector4(targetPosition[0],-targetPosition[1],targetPosition[2],0) - positions[0]) * springStiffness - velocities[0] * springDamping) / (invMass);                    
Oni.AddParticleExternalForce(picker.Cloth.Solver.OniSolver,ref force,new int[]{solverIndex},1);
}

}
}

void Picker_OnParticleDragged (object sender, ObiClothPicker.ParticlePickEventArgs e)
{
pickArgs = e;
}

void Picker_OnParticleReleased (object sender, ObiClothPicker.ParticlePickEventArgs e)
{
pickArgs = null;
}

}
}

Lastly, in ObiClothDragger.cs, in fixed update, while calculating and applying spring force, I tried changing the line -
Code:
Vector4 force = ((new Vector4(targetPosition[0],-targetPosition[1],targetPosition[2],0) - positions[0]) * springStiffness - velocities[0] * springDamping) / (invMass);

to this...

Code:
Vector4 force = ((new Vector4(targetPosition[0],-targetPosition[1]-10,targetPosition[2],0) - positions[0]) * springStiffness - velocities[0] * springDamping) / (invMass);

..so that I could provide an increased depth effect at the point of touch (instead of using a splat map, to get required "pressed" visual effect). But it makes the whole cloth glitch out and disappear instantly. How do I increase the depth of the "press"? For now I was assuming "targetPosition[1]" is the Y-Position of the press.

Hi,

There's lots of things off or downright wrong here, most of them related to basic programming concepts or Unity API usage. I'm afraid I cannot offer support beyond this, as it is not Obi-related. However I will try to outline some of the issues to help you out:

Code:
          for (int i = 0; i < Input.touchCount; i++)
          {
              meshCollider.enabled = true;

              GameObject.Destroy(currentCollisionMesh);
              currentCollisionMesh = GameObject.Instantiate(cloth.clothMesh);
              meshCollider.sharedMesh = currentCollisionMesh;
              ....

              meshCollider.enabled = false;
          }
#endif

Here you're destroying and re-creating the mesh collider used for raycast and input detection for each finger. If you have 3 fingers touching the screen, you're destroying and instantiating the entire mesh 3 times per frame. While technically ok, this is terrible for performance. You should start the for loop after creating the mesh just once.

Code:
if (Input.touchCount < 1)
          {
              if (OnParticleReleased != null)
              {
                  Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
                  OnParticleReleased(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
              }

              pickedParticleIndex = -1;
}

Here you're calling OnParticleReleased just once, after the user has lifted all fingers from the screen. This should be called once for each finger that got lifted off.

Code:
// get particle index:
                  if (closestVertex >= 0 && closestVertex < cloth.topology.visualMap.Length)
                  {

                      pickedParticleIndex = cloth.topology.visualMap[closestVertex];
                      pickedParticleDepth = Mathf.Abs((cloth.transform.TransformPoint(vertices[closestVertex]) - Camera.main.transform.position).z);

There's a deeper issue: you should have arrays (pickedParticleIndices[] and pickedParticleDepths[]) to support multitouch, instead of a single variable. How else can you identify each particle when multiple ones are being dragged? The way the code is written, the last finger to touch the screen will overwrite pickedParticleIndex. Only one particle will be picked.

Code:
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

In this line and several others, you're still using Input.mousePosition, mixing it with Input.touchCount. You should use Input.GetTouch(index) instead (see https://docs.unity3d.com/ScriptReference/Touch.html), as the mousePosition returns the average of all finger positions when on a touchscreen (read Unity's documentation), which is precisely the result you're getting.
Reply
#20
(12-09-2018, 04:45 PM)josemendez Wrote: Hi,

There's lots of things off or downright wrong here, most of them related to basic programming concepts or Unity API usage. I'm afraid I cannot offer support beyond this, as it is not Obi-related. However I will try to outline some of the issues to help you out:

Code:
          for (int i = 0; i < Input.touchCount; i++)
          {
              meshCollider.enabled = true;

              GameObject.Destroy(currentCollisionMesh);
              currentCollisionMesh = GameObject.Instantiate(cloth.clothMesh);
              meshCollider.sharedMesh = currentCollisionMesh;
              ....

              meshCollider.enabled = false;
          }
#endif

Here you're destroying and re-creating the mesh collider used for raycast and input detection for each finger. If you have 3 fingers touching the screen, you're destroying and instantiating the entire mesh 3 times per frame. While technically ok, this is terrible for performance. You should start the for loop after creating the mesh just once.

Code:
if (Input.touchCount < 1)
          {
              if (OnParticleReleased != null)
              {
                  Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));
                  OnParticleReleased(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
              }

              pickedParticleIndex = -1;
}

Here you're calling OnParticleReleased just once, after the user has lifted all fingers from the screen. This should be called once for each finger that got lifted off.

Code:
// get particle index:
                  if (closestVertex >= 0 && closestVertex < cloth.topology.visualMap.Length)
                  {

                      pickedParticleIndex = cloth.topology.visualMap[closestVertex];
                      pickedParticleDepth = Mathf.Abs((cloth.transform.TransformPoint(vertices[closestVertex]) - Camera.main.transform.position).z);

There's a deeper issue: you should have arrays (pickedParticleIndices[] and pickedParticleDepths[]) to support multitouch, instead of a single variable. How else can you identify each particle when multiple ones are being dragged? The way the code is written, the last finger to touch the screen will overwrite pickedParticleIndex. Only one particle will be picked.

Code:
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

In this line and several others, you're still using Input.mousePosition, mixing it with Input.touchCount. You should use Input.GetTouch(index) instead (see https://docs.unity3d.com/ScriptReference/Touch.html), as the mousePosition returns the average of all finger positions when on a touchscreen (read Unity's documentation), which is precisely the result you're getting.

All that information is very useful, thanks. Again, my coding skills are very basic, apologies if I mixed the thread up with programming questions.

So I've been playing around with various parameters for the slime cloth and as mentioned before, I tried using height, normal and occlusion maps on materials to get the "depth" and "folds" effects.

Nothing I did could reproduce this effect.

As you can see, ignoring the colour and lighting changes, the slime shown here reforms very slowly and creates actual folds in the object. How do I create these "folds"? No amount of material modification seems to help, as what's shown here seems to be true mesh manipulation.

Next, instead of using a flat square plane mesh, I tried out using something like this -


.png   mesh.PNG (Size: 5.93 KB / Downloads: 43)

This is a low poly "rough surface" mesh. I fixed all of it's edge particles, then initialized it as a new cloth. But instead of deforming properly on touch, it creates huge graphical glitches (Infinite stretching on mouse touch, etc).

Is there no way I can reproduce the aforementioned style of slime correctly on cloth objects? I've tried alternating everything, from damping, slack, stretch, etc. but in vain.
Reply