Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  How to add particles to existing cloth object
#1
Pregunta 
Hi,
I would like to know how to add a particle system to an existing cloth object. 

For instance, if I have a cloth dynamically changing it's mesh shape, I would like to add a particle system on top of it which also follows the shape of the cloth mesh. Think of bubbles on top of a water surface.

I've tried using unity's skinned mesh bake option and updating particle system input mesh according to it, but that exists only for skinned mesh renderers. I know I can use a custom shader or complex decal assets for this, but as I'm unfamiliar with shader coding and would like to keep things simple, I would like to know if there's any existing way to utilize via this asset.

Thanks.
Reply
#2
(09-10-2018, 08:35 AM)arrnav96 Wrote: Hi,
I would like to know how to add a particle system to an existing cloth object. 

For instance, if I have a cloth dynamically changing it's mesh shape, I would like to add a particle system on top of it which also follows the shape of the cloth mesh. Think of bubbles on top of a water surface.

I've tried using unity's skinned mesh bake option and updating particle system input mesh according to it, but that exists only for skinned mesh renderers. I know I can use a custom shader or complex decal assets for this, but as I'm unfamiliar with shader coding and would like to keep things simple, I would like to know if there's any existing way to utilize via this asset.

Thanks.

Being a cloth simulator, Obi does not provide any way to deal with this.

You will have to code your own solution. The simplest thing that comes to mind is using barycentric coordinates to store each particle's position relative to its closest triangle, and update them as the mesh deforms. You can get the deformed cloth mesh each frame like this:

Code:
obiClothComponent.clothMesh

cheers,
Reply
#3
(09-10-2018, 08:57 AM)josemendez Wrote: Being a cloth simulator, Obi does not provide any way to deal with this.

You will have to code your own solution. The simplest thing that comes to mind is using barycentric coordinates to store each particle's position relative to its closest triangle, and update them as the mesh deforms. You can get the deformed cloth mesh each frame like this:

Code:
obiClothComponent.clothMesh

cheers,

Got it!

Also, is there a way to reliably drag the cloth using mouse input? The current unmodified ObiClothDragger script does quite "drag" around the selected vertex with the mouse.

Specifically, I want the selected vertex on GetMouseButtonDown(0) to move under the exact mouse pointer's position on GetMouseButton(0).

Thanks
Reply
#4
(09-10-2018, 02:25 PM)arrnav96 Wrote: Got it!

Also, is there a way to reliably drag the cloth using mouse input? The current unmodified ObiClothDragger script does quite "drag" around the selected vertex with the mouse.

Specifically, I want the selected vertex on GetMouseButtonDown(0) to move under the exact mouse pointer's position on GetMouseButton(0).

Thanks

Yep. Instead of using a spring force to move the particle around (which is what the dragger currently does), modify it to set the particle position directly using Oni.SetParticlePositions(). See:
http://obi.virtualmethodstudio.com/tutor...icles.html
Reply
#5
(09-10-2018, 02:28 PM)josemendez Wrote: Yep. Instead of using a spring force to move the particle around (which is what the dragger currently does), modify it to set the particle position directly using Oni.SetParticlePositions(). See:
http://obi.virtualmethodstudio.com/tutor...icles.html

Unfortunately it still doesn't change it's behaviour. The cloth point only drags itself as shown here. I want the pickedParticle to be dragged fully with mouse pointer. 

The edge particles of this mesh are fixed.

These are my current scripts -

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;
            }
        }

       GameObject FingerTrailSphere;

       public float FingerRadius = 20;
   
        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;

       [HideInInspector]
       public float durationsTouch;

       Vector3 tpos;

       public ObiClothBase Cloth{
            get{return cloth;}
        }

       ObiActor actor;

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

           FingerTrailSphere = GameObject.Find("FingerFollowTrailObject");
       }

        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:

           /*Touch myTouch = Input.GetTouch(0);

           Touch[] myTouches = Input.touches;
           for (int i = 0; i < Input.touchCount; i++)
           {
               Debug.Log(Input.touchCount);
           }*/
           if (Input.GetMouseButtonDown(0))
           {                            
               meshCollider.enabled = true;

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

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

               tpos = ray.GetPoint(10);

               FingerTrailSphere.transform.position = tpos;

                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 i = 0; i < 3; ++i)
                   {
                        int vertex = tris[hitInfo.triangleIndex*3+i];
                        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.GetMouseButton(0))
           {
               Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

               tpos = ray.GetPoint(0);
           }

           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));
                   Vector3 worldPosition = tpos;
                   OnParticleDragged(this,new ParticlePickEventArgs(pickedParticleIndex,worldPosition));
                }
               else if (OnParticleHeld != null)
               {
                   Vector3 worldPosition = tpos;
                    //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;
        }
   }
}

ObiClothDragger.cs -

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

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

        private ObiClothPicker picker;
        private ObiClothPicker.ParticlePickEventArgs pickArgs;

       [HideInInspector]
       public enum TypeOfSlime { Liquid, Sticky, VerySticky};

       public TypeOfSlime SlimeTypeSelector;
       public float FingerRadius = 50;
       public float FingerDepthDurationFactor = 1.5f;

       Vector3 tpos;

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

        void OnDisable()
        {
            picker.OnParticlePicked -= Picker_OnParticlePicked;
            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)) * (picker.durationsTouch/FingerDepthDurationFactor);                    
                   //Oni.AddParticleExternalForce(picker.Cloth.Solver.OniSolver,ref force,new int[]{solverIndex},1);
                   Oni.SetParticlePositions(picker.Cloth.Solver.OniSolver, new Vector4[] { tpos }, 1, pickArgs.particleIndex);
               }        
            }
        }

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

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

       void Update()
       {
           if (Input.GetMouseButton(0))
           {
               Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

               tpos = ray.GetPoint(10);

               if (picker.durationsTouch <= 4)
               {
                   picker.durationsTouch += Time.deltaTime;
               }
           }
       }
       
       void Picker_OnParticleReleased (object sender, ObiClothPicker.ParticlePickEventArgs e)
        {
           picker.durationsTouch = 0;

           Oni.SetParticleVelocities(picker.Cloth.Solver.OniSolver, new Vector4[] { new Vector4(0, 12, 0, 0) }, 1, picker.Cloth.particleIndices[e.particleIndex]);

           if (SlimeTypeSelector == TypeOfSlime.Liquid)
           {
               pickArgs = null;
           }
           else if (SlimeTypeSelector == TypeOfSlime.Sticky)
           {
               Oni.SetParticleInverseMasses(picker.Cloth.Solver.OniSolver, new float[] { 0.5f }, 1, picker.Cloth.particleIndices[e.particleIndex]);
               //Oni.SetParticleVelocities(picker.Cloth.Solver.OniSolver, new Vector4[] { new Vector4(1,2,1,0) }, 2, picker.Cloth.particleIndices[e.particleIndex]);
               picker.Cloth.Solver.RelinquishRenderablePositions();
           }
           else if (SlimeTypeSelector == TypeOfSlime.VerySticky)
           {
               Oni.SetParticleInverseMasses(picker.Cloth.Solver.OniSolver, new float[] { 0 }, 1, picker.Cloth.particleIndices[e.particleIndex]);
               Oni.SetParticleVelocities(picker.Cloth.Solver.OniSolver, new Vector4[] { Vector4.zero }, 1, picker.Cloth.particleIndices[e.particleIndex]);
               picker.Cloth.Solver.RelinquishRenderablePositions();
           }
       }

    }
}
Reply
#6
(09-10-2018, 05:46 PM)arrnav96 Wrote: Unfortunately it still doesn't change it's behaviour. The cloth point only drags itself as shown here. I want the pickedParticle to be dragged fully with mouse pointer. 

The edge particles of this mesh are fixed.

These are my current scripts -

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;
}
}

       GameObject FingerTrailSphere;

       public float FingerRadius = 20;
   
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;

       [HideInInspector]
       public float durationsTouch;

       Vector3 tpos;

       public ObiClothBase Cloth{
get{return cloth;}
}

       ObiActor actor;

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

           FingerTrailSphere = GameObject.Find("FingerFollowTrailObject");
       }

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:

           /*Touch myTouch = Input.GetTouch(0);

           Touch[] myTouches = Input.touches;
           for (int i = 0; i < Input.touchCount; i++)
           {
               Debug.Log(Input.touchCount);
           }*/
           if (Input.GetMouseButtonDown(0))
           {                            
               meshCollider.enabled = true;

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

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

               tpos = ray.GetPoint(10);

               FingerTrailSphere.transform.position = tpos;

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 i = 0; i < 3; ++i)
                   {
int vertex = tris[hitInfo.triangleIndex*3+i];
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.GetMouseButton(0))
           {
               Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

               tpos = ray.GetPoint(0);
           }

           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));
                   Vector3 worldPosition = tpos;
                   OnParticleDragged(this,new ParticlePickEventArgs(pickedParticleIndex,worldPosition));
}
               else if (OnParticleHeld != null)
               {
                   Vector3 worldPosition = tpos;
//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;
}
   }
}

ObiClothDragger.cs -

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

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

private ObiClothPicker picker;
private ObiClothPicker.ParticlePickEventArgs pickArgs;

       [HideInInspector]
       public enum TypeOfSlime { Liquid, Sticky, VerySticky};

       public TypeOfSlime SlimeTypeSelector;
       public float FingerRadius = 50;
       public float FingerDepthDurationFactor = 1.5f;

       Vector3 tpos;

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

void OnDisable()
{
picker.OnParticlePicked -= Picker_OnParticlePicked;
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)) * (picker.durationsTouch/FingerDepthDurationFactor);                    
                   //Oni.AddParticleExternalForce(picker.Cloth.Solver.OniSolver,ref force,new int[]{solverIndex},1);
                   Oni.SetParticlePositions(picker.Cloth.Solver.OniSolver, new Vector4[] { tpos }, 1, pickArgs.particleIndex);
               }
}
}

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

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

       void Update()
       {
           if (Input.GetMouseButton(0))
           {
               Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

               tpos = ray.GetPoint(10);

               if (picker.durationsTouch <= 4)
               {
                   picker.durationsTouch += Time.deltaTime;
               }
           }
       }
       
       void Picker_OnParticleReleased (object sender, ObiClothPicker.ParticlePickEventArgs e)
{
           picker.durationsTouch = 0;

           Oni.SetParticleVelocities(picker.Cloth.Solver.OniSolver, new Vector4[] { new Vector4(0, 12, 0, 0) }, 1, picker.Cloth.particleIndices[e.particleIndex]);

           if (SlimeTypeSelector == TypeOfSlime.Liquid)
           {
               pickArgs = null;
           }
           else if (SlimeTypeSelector == TypeOfSlime.Sticky)
           {
               Oni.SetParticleInverseMasses(picker.Cloth.Solver.OniSolver, new float[] { 0.5f }, 1, picker.Cloth.particleIndices[e.particleIndex]);
               //Oni.SetParticleVelocities(picker.Cloth.Solver.OniSolver, new Vector4[] { new Vector4(1,2,1,0) }, 2, picker.Cloth.particleIndices[e.particleIndex]);
               picker.Cloth.Solver.RelinquishRenderablePositions();
           }
           else if (SlimeTypeSelector == TypeOfSlime.VerySticky)
           {
               Oni.SetParticleInverseMasses(picker.Cloth.Solver.OniSolver, new float[] { 0 }, 1, picker.Cloth.particleIndices[e.particleIndex]);
               Oni.SetParticleVelocities(picker.Cloth.Solver.OniSolver, new Vector4[] { Vector4.zero }, 1, picker.Cloth.particleIndices[e.particleIndex]);
               picker.Cloth.Solver.RelinquishRenderablePositions();
           }
       }

}
}

Well, the behaviour shown in the video is exactly what the script as it is written is supposed to do: drag a single cloth particle around.

I don't understand what you mean by "I want the pickedParticle to be dragged fully". If you want more particles around the pickedParticle to be dragged you'll have to set their positions as well.
Reply
#7
(09-10-2018, 06:26 PM)josemendez Wrote: Well, the behaviour shown in the video is exactly what the script as it is written is supposed to do: drag a single cloth particle around.

I don't understand what you mean by "I want the pickedParticle to be dragged fully". If you want more particles around the pickedParticle to be dragged you'll have to set their positions as well.

What I mean is the selected particle should be dragged in such a way that the particle stays exactly under the mouse pointer until mouse button is released. 

In the video, as the script is intended to do, the particle, once selected, only moves it's direction partially according to where the mouse pointer is. Not it's actual position. It should be under the mouse pointer.
Reply
#8
(09-10-2018, 07:23 PM)arrnav96 Wrote: What I mean is the selected particle should be dragged in such a way that the particle stays exactly under the mouse pointer until mouse button is released. 

In the video, as the script is intended to do, the particle, once selected, only moves it's direction partially according to where the mouse pointer is. Not it's actual position. It should be under the mouse pointer.

Apologies, my last video did not show the mouse pointer. 

This is the updated video showing the exact problem.
Reply
#9
(10-10-2018, 02:47 AM)arrnav96 Wrote: Apologies, my last video did not show the mouse pointer. 

This is the updated video showing the exact problem.

Hi,

I've taken a closer look at your code. Currently it will set the particle position, but will not set its velocity or prevent the rest of the particles from acting upon it. So what you're getting is the cloth still pulling from the particle when you try to set its position, then drifting away due to non-zero velocity.

To get it working as you want, you need to:
- When the particle is picked, set its inverse mass to zero (that is equivalent to setting its mass to infinite, so that constraints cannot act upon it -->the rest of the cloth will not pull it back). Also set its velocity to zero (so that it does not drift off from whatever position we drag it to).
- When dragging the particle, set its position to the cursor position.
- When releasing the particle, set its inverse mass to whatever it was prior to being picked. This will allow it to be affected by the cloth constraints once again.

Code:
Code:
      float oldInvMass;
      void Picker_OnParticlePicked(object sender, NewPicker.ParticlePickEventArgs e)
      {
          oldInvMass = picker.Cloth.invMasses[e.particleIndex] * picker.Cloth.areaContribution[e.particleIndex];
          Oni.SetParticlePositions(picker.Cloth.Solver.OniSolver, new Vector4[] { e.worldPosition }, 1, e.particleIndex);
          Oni.SetParticleVelocities(picker.Cloth.Solver.OniSolver,new Vector4[] { Vector4.zero },1,e.particleIndex);
          Oni.SetParticleInverseMasses(picker.Cloth.Solver.OniSolver,new float[]{0},1,e.particleIndex);
      }

      void Picker_OnParticleDragged (object sender, NewPicker.ParticlePickEventArgs e)
      {
          Oni.SetParticlePositions(picker.Cloth.Solver.OniSolver, new Vector4[] { e.worldPosition }, 1, e.particleIndex);      
      }

      void Picker_OnParticleReleased (object sender, NewPicker.ParticlePickEventArgs e)
      {
          Oni.SetParticleInverseMasses(picker.Cloth.Solver.OniSolver,new float[]{oldInvMass},1,e.particleIndex);
      }

Note that in your code, the picker's OnParticleDragged won't be called as long as the user is dragging (which is exactly the opposite of what you want). You need to get it out of the else clause after Input.GetMouseButton(0).

Also, I've noted that depending on what type the slime is, you set the particle inverse mass to 0.5f or 0 when the user releases it. Setting it to 0.5f will have the effect of making the point released by the user heavier/lighter than the rest of the cloth, which is something you probably want to avoid. Setting it to 0 will disable constraint simulation for that particle (leaving it driven by inertia), which does not make much sense in this context.
Reply
#10
I've followed your instructions and these are the changes I made to the code. I get a very glitchy result now where the selected particle does follow the pointer, but absolutely none of the other particles are affected by it's movement. 
The selected particle appears to simply stretch the face it belongs to.
I would like the surrounding particles to naturally move too if the selected particle is moved, without restraining the selected particle.
Picker.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;
}
}

       GameObject FingerTrailSphere;

       public float FingerRadius = 20;
   
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;

       [HideInInspector]
       public float durationsTouch;

       Vector3 tpos;

       public ObiClothBase Cloth{
get{return cloth;}
}

       ObiActor actor;

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

           FingerTrailSphere = GameObject.Find("FingerFollowTrailObject");
       }

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:

           /*Touch myTouch = Input.GetTouch(0);

           Touch[] myTouches = Input.touches;
           for (int i = 0; i < Input.touchCount; i++)
           {
               Debug.Log(Input.touchCount);
           }*/
           if (Input.GetMouseButtonDown(0))
           {                            
               meshCollider.enabled = true;

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

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

               tpos = ray.GetPoint(10);

               FingerTrailSphere.transform.position = tpos;

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 i = 0; i < 3; ++i)
                   {
int vertex = tris[hitInfo.triangleIndex*3+i];
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.GetMouseButton(0))
           {
               Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

               tpos = ray.GetPoint(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));
                   Vector3 worldPosition = tpos;
                   OnParticleDragged(this, new ParticlePickEventArgs(pickedParticleIndex, worldPosition));
               }
           }

           else if (pickedParticleIndex >= 0)
           {                
               if (OnParticleHeld != null)
               {
                   Vector3 worldPosition = tpos;
//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;
}
   }
}

Dragger.cs -
Code:
using System;
using UnityEngine;
using System.Collections;

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

private ObiClothPicker picker;
private ObiClothPicker.ParticlePickEventArgs pickArgs;

       [HideInInspector]
       public enum TypeOfSlime { Liquid, Sticky, VerySticky};

       public TypeOfSlime SlimeTypeSelector;
       public float FingerRadius = 50;
       public float FingerDepthDurationFactor = 1.5f;

       Vector3 tpos;

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

void OnDisable()
{
picker.OnParticlePicked -= Picker_OnParticlePicked;
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)) * (picker.durationsTouch/FingerDepthDurationFactor);                    
                   //Oni.AddParticleExternalForce(picker.Cloth.Solver.OniSolver,ref force,new int[]{solverIndex},1);
                   Oni.SetParticlePositions(picker.Cloth.Solver.OniSolver, new Vector4[] { tpos }, 1, pickArgs.particleIndex);
               }
}
}

       float oldInvMass;

       void Picker_OnParticlePicked(object sender, ObiClothPicker.ParticlePickEventArgs e)
       {
           oldInvMass = picker.Cloth.invMasses[e.particleIndex] * picker.Cloth.areaContribution[e.particleIndex];
           Oni.SetParticlePositions(picker.Cloth.Solver.OniSolver, new Vector4[] { e.worldPosition }, 1, e.particleIndex);
           Oni.SetParticleVelocities(picker.Cloth.Solver.OniSolver, new Vector4[] { Vector4.zero }, 1, e.particleIndex);
           Oni.SetParticleInverseMasses(picker.Cloth.Solver.OniSolver, new float[] { 0 }, 1, e.particleIndex);
       }

       void Picker_OnParticleDragged(object sender, ObiClothPicker.ParticlePickEventArgs e)
       {
           Oni.SetParticlePositions(picker.Cloth.Solver.OniSolver, new Vector4[] { e.worldPosition }, 1, e.particleIndex);
       }

       void Picker_OnParticleReleased(object sender, ObiClothPicker.ParticlePickEventArgs e)
       {
           Oni.SetParticleInverseMasses(picker.Cloth.Solver.OniSolver, new float[] { oldInvMass }, 1, e.particleIndex);
       }

       void Update()
       {
           if (Input.GetMouseButton(0))
           {
               Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

               tpos = ray.GetPoint(10);

               if (picker.durationsTouch <= 4)
               {
                   picker.durationsTouch += Time.deltaTime;
               }
           }
       }

}
}
Reply