Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
I would like to know the optimized way to catch objects in 'SteamVR' using 'SoftBody'
#1
Bombilla 
Hello, I'm writing because I want to make Softbody work even if I buy Softbody and grab an object.

I know Obi Solver should be a parent and Obi Softbody should be a child.

So if you fall from the top, you get the force to crush the object and bring it back to its original state.

Like the BallPool scene in the sample.

But there's a problem.

If you grab an object with a controller, The problem is that the object comes out of the Solver and enters the controller's child.

So there's something else I've done.

Like the picture I attached, I made a solver object at the top, made a component, and if I put all the objects in the scene into sub-objects and execute them, the object will not be caught.

Is there a way to make SoftBody apply even if I catch an object?

And the file names components_1 to components_4 are the components used for the catch object.

I want you to tell me what I'm doing wrong.

Thank you.
Reply
#2
Hi,

Reparenting a deformable object (not just a softbody) would just not work.  Not only because it would no longer be inside the solver, but because determining the rotation/position/scale of a deformable object using a single transform is just not possible, so even if the softbody could be simulated outside of a solver, parenting it to a transform would not "grab" it. This question has come up in the forums tens of times, usually in the form of "can I scale a softbody?", "can I set the position of a cloth?" or "can I change the length of a rope by scaling it"? The answer to all these is the same: a transform is a single 4x4 matrix, and you can't determine the size, shape, orientation and position of a deformable object with just one matrix.

A softbody is a collection of rigid particles, each one with its own position and orientation, held together by constraints. So to pick up the softbody, you need to pick up one (or a few) of its particles.

Easiest way to do this is to detect which particles are in contact with a collider/trigger, then fix them by setting their mass to infinite and set their position relative to the collider. This is what the sample script ObiColliderGrabber does (included in 5.2 and up): it will detect all particles touching a collider, then "grab" them when you call its Grab() method.

I'm pasting the code here, in case you have 5.0 or 5.1. Should work there too:

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

/**
* Sample component that makes a collider "grab" any particle it touches (regardless of which Actor it belongs to).
*/
[RequireComponent(typeof(ObiCollider))]
public class ObiColliderGrabber : MonoBehaviour
{

    public ObiSolver solver;

    /**
     * Helper class that stores the index of a particle in the solver, its position in the grabber's local space, and its inverse mass previous to being grabbed.
     * This makes it easy to tell if a particle has been grabbed, update its position while grabbing, and restore its mass after being released.
     */
    private class GrabbedParticle : IEqualityComparer<GrabbedParticle>
    {
        public int index;
        public float invMass;
        public Vector3 localPosition;

        public GrabbedParticle(int index, float invMass)
        {
            this.index = index;
            this.invMass = invMass;
        }

        public bool Equals(GrabbedParticle x, GrabbedParticle y)
        {
            return x.index == y.index;
        }

        public int GetHashCode(GrabbedParticle obj)
        {
            return index;
        }
    }

    private Obi.ObiSolver.ObiCollisionEventArgs collisionEvent;                                  /**< store the current collision event*/
    private ObiCollider localCollider;                                                           /**< the collider on this gameObject.*/
    private HashSet<GrabbedParticle> grabbedParticles = new HashSet<GrabbedParticle>();          /**< set to store all currently grabbed particles.*/
    private HashSet<ObiSoftbody> grabbedSoftbodies = new HashSet<ObiSoftbody>();                 /**< set of softbodies grabbed during this step.*/
    private Matrix4x4 grabber2Solver;
    private Matrix4x4 solver2Grabber;

    private void Awake()
    {
        localCollider = GetComponent<ObiCollider>();
    }

    private void OnEnable()
    {
        solver.OnCollision += Solver_OnCollision;
    }

    private void OnDisable()
    {
        solver.OnCollision -= Solver_OnCollision;
    }

    private void Solver_OnCollision(object sender, Obi.ObiSolver.ObiCollisionEventArgs e)
    {
        // Calculate transform matrix from grabber to world space (Note: if using local space simulation, postmultiply with solver.transform.localToWorldMatrix)
        solver2Grabber = transform.worldToLocalMatrix * solver.transform.localToWorldMatrix;

        // and its inverse:
        grabber2Solver = solver2Grabber.inverse;

        collisionEvent = e;
    }

    private void UpdateRestShapeMatching()
    {
        // Update rest shape matching of all grabbed softbodies:
        foreach (ObiSoftbody softbody in grabbedSoftbodies)
        {
            var constraints = softbody.GetConstraintsByType(Oni.ConstraintType.ShapeMatching);
            if (constraints != null)
            {
                var batches = constraints.GetBatchInterfaces();
                foreach (ObiShapeMatchingConstraintsBatch batch in batches)
                    Oni.CalculateRestShapeMatching(solver.OniSolver, batch.oniBatch);
            }
        }
    }

    /**
     * Creates and stores a GrabbedParticle from the particle at the given index.
     * Returns true if we sucessfully grabbed a particle, false if the particle was already grabbed.
     */
    private bool GrabParticle(int index)
    {
        GrabbedParticle p = new GrabbedParticle(index, solver.invMasses[index]);

        // in case this particle has not been grabbed yet:
        if (!grabbedParticles.Contains(p))
        {
            // record the particle's position relative to the grabber, and store it.
            p.localPosition = solver2Grabber.MultiplyPoint3x4(solver.positions[index]);
            grabbedParticles.Add(p);

            // Set inv mass and velocity to zero:
            solver.invMasses[index] = 0;
            solver.velocities[index] = Vector4.zero;

            return true;
        }

        return false;
    }

    /**
     * Grabs all particles currently touching the grabber.
     */
    public void Grab()
    {
        grabbedSoftbodies.Clear();

        foreach (Oni.Contact contact in collisionEvent.contacts)
        {
            // this one is an actual collision:
            if (contact.distance < 0.01f)
            {
                Component contactCollider;
                if (ObiCollider.idToCollider.TryGetValue(contact.other, out contactCollider))
                {
                    // if the current contact references our collider, proceed to grab the particle.
                    if (contactCollider == localCollider.SourceCollider)
                    {
                        // try to grab the particle, if not already grabbed.
                        if (GrabParticle(contact.particle))
                        {
                            // we want to know if we grabbed a softbody, to update its rest shape.
                            var softbody = solver.particleToActor[contact.particle].actor as ObiSoftbody;
                            if (softbody != null)
                                grabbedSoftbodies.Add(softbody);
                        }
                    }
                }
            }
        }

        UpdateRestShapeMatching();
    }

    /**
     * Releases all currently grabbed particles. This boils down to simply resetting their invMass.
     */
    public void Release()
    {
        // Restore the inverse mass of all grabbed particles, so dynamics affect them.
        foreach (GrabbedParticle p in grabbedParticles)
            solver.invMasses[p.index] = p.invMass;

        grabbedParticles.Clear();

        // Also update rest shape matching:
        UpdateRestShapeMatching();
        grabbedSoftbodies.Clear();
    }

    /**
     * Updates the position of the grabbed particles.
     */
    private void FixedUpdate()
    {
        foreach (GrabbedParticle p in grabbedParticles)
            solver.positions[p.index] = grabber2Solver.MultiplyPoint3x4(p.localPosition);
    }

    /**
     * Just for convenience. Ideally, this should not be part of this component.
     * You're expected to control the Grabber from outside.
     */
    public void Update()
    {
        if (Input.GetKeyDown(KeyCode.G))
        {
            Grab();
        }

        if (Input.GetKeyDown(KeyCode.R))
        {
            Release();
        }
           
    }
}
Reply
#3
(17-06-2020, 08:12 AM)josemendez Wrote: Hi,

Reparenting a deformable object (not just a softbody) would just not work.  Not only because it would no longer be inside the solver, but because determining the rotation/position/scale of a deformable object using a single transform is just not possible, so even if the softbody could be simulated outside of a solver, parenting it to a transform would not "grab" it. This question has come up in the forums tens of times, usually in the form of "can I scale a softbody?", "can I set the position of a cloth?" or "can I change the length of a rope by scaling it"? The answer to all these is the same: a transform is a single 4x4 matrix, and you can't determine the size, shape, orientation and position of a deformable object with just one matrix.

A softbody is a collection of rigid particles, each one with its own position and orientation, held together by constraints. So to pick up the softbody, you need to pick up one (or a few) of its particles.

Easiest way to do this is to detect which particles are in contact with a collider/trigger, then fix them by setting their mass to infinite and set their position relative to the collider. This is what the sample script ObiColliderGrabber does (included in 5.2 and up): it will detect all particles touching a collider, then "grab" them when you call its Grab() method.

I'm pasting the code here, in case you have 5.0 or 5.1. Should work there too:

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

/**
* Sample component that makes a collider "grab" any particle it touches (regardless of which Actor it belongs to).
*/
[RequireComponent(typeof(ObiCollider))]
public class ObiColliderGrabber : MonoBehaviour
{

    public ObiSolver solver;

    /**
     * Helper class that stores the index of a particle in the solver, its position in the grabber's local space, and its inverse mass previous to being grabbed.
     * This makes it easy to tell if a particle has been grabbed, update its position while grabbing, and restore its mass after being released.
     */
    private class GrabbedParticle : IEqualityComparer<GrabbedParticle>
    {
        public int index;
        public float invMass;
        public Vector3 localPosition;

        public GrabbedParticle(int index, float invMass)
        {
            this.index = index;
            this.invMass = invMass;
        }

        public bool Equals(GrabbedParticle x, GrabbedParticle y)
        {
            return x.index == y.index;
        }

        public int GetHashCode(GrabbedParticle obj)
        {
            return index;
        }
    }

    private Obi.ObiSolver.ObiCollisionEventArgs collisionEvent;                                  /**< store the current collision event*/
    private ObiCollider localCollider;                                                           /**< the collider on this gameObject.*/
    private HashSet<GrabbedParticle> grabbedParticles = new HashSet<GrabbedParticle>();          /**< set to store all currently grabbed particles.*/
    private HashSet<ObiSoftbody> grabbedSoftbodies = new HashSet<ObiSoftbody>();                 /**< set of softbodies grabbed during this step.*/
    private Matrix4x4 grabber2Solver;
    private Matrix4x4 solver2Grabber;

    private void Awake()
    {
        localCollider = GetComponent<ObiCollider>();
    }

    private void OnEnable()
    {
        solver.OnCollision += Solver_OnCollision;
    }

    private void OnDisable()
    {
        solver.OnCollision -= Solver_OnCollision;
    }

    private void Solver_OnCollision(object sender, Obi.ObiSolver.ObiCollisionEventArgs e)
    {
        // Calculate transform matrix from grabber to world space (Note: if using local space simulation, postmultiply with solver.transform.localToWorldMatrix)
        solver2Grabber = transform.worldToLocalMatrix * solver.transform.localToWorldMatrix;

        // and its inverse:
        grabber2Solver = solver2Grabber.inverse;

        collisionEvent = e;
    }

    private void UpdateRestShapeMatching()
    {
        // Update rest shape matching of all grabbed softbodies:
        foreach (ObiSoftbody softbody in grabbedSoftbodies)
        {
            var constraints = softbody.GetConstraintsByType(Oni.ConstraintType.ShapeMatching);
            if (constraints != null)
            {
                var batches = constraints.GetBatchInterfaces();
                foreach (ObiShapeMatchingConstraintsBatch batch in batches)
                    Oni.CalculateRestShapeMatching(solver.OniSolver, batch.oniBatch);
            }
        }
    }

    /**
     * Creates and stores a GrabbedParticle from the particle at the given index.
     * Returns true if we sucessfully grabbed a particle, false if the particle was already grabbed.
     */
    private bool GrabParticle(int index)
    {
        GrabbedParticle p = new GrabbedParticle(index, solver.invMasses[index]);

        // in case this particle has not been grabbed yet:
        if (!grabbedParticles.Contains(p))
        {
            // record the particle's position relative to the grabber, and store it.
            p.localPosition = solver2Grabber.MultiplyPoint3x4(solver.positions[index]);
            grabbedParticles.Add(p);

            // Set inv mass and velocity to zero:
            solver.invMasses[index] = 0;
            solver.velocities[index] = Vector4.zero;

            return true;
        }

        return false;
    }

    /**
     * Grabs all particles currently touching the grabber.
     */
    public void Grab()
    {
        grabbedSoftbodies.Clear();

        foreach (Oni.Contact contact in collisionEvent.contacts)
        {
            // this one is an actual collision:
            if (contact.distance < 0.01f)
            {
                Component contactCollider;
                if (ObiCollider.idToCollider.TryGetValue(contact.other, out contactCollider))
                {
                    // if the current contact references our collider, proceed to grab the particle.
                    if (contactCollider == localCollider.SourceCollider)
                    {
                        // try to grab the particle, if not already grabbed.
                        if (GrabParticle(contact.particle))
                        {
                            // we want to know if we grabbed a softbody, to update its rest shape.
                            var softbody = solver.particleToActor[contact.particle].actor as ObiSoftbody;
                            if (softbody != null)
                                grabbedSoftbodies.Add(softbody);
                        }
                    }
                }
            }
        }

        UpdateRestShapeMatching();
    }

    /**
     * Releases all currently grabbed particles. This boils down to simply resetting their invMass.
     */
    public void Release()
    {
        // Restore the inverse mass of all grabbed particles, so dynamics affect them.
        foreach (GrabbedParticle p in grabbedParticles)
            solver.invMasses[p.index] = p.invMass;

        grabbedParticles.Clear();

        // Also update rest shape matching:
        UpdateRestShapeMatching();
        grabbedSoftbodies.Clear();
    }

    /**
     * Updates the position of the grabbed particles.
     */
    private void FixedUpdate()
    {
        foreach (GrabbedParticle p in grabbedParticles)
            solver.positions[p.index] = grabber2Solver.MultiplyPoint3x4(p.localPosition);
    }

    /**
     * Just for convenience. Ideally, this should not be part of this component.
     * You're expected to control the Grabber from outside.
     */
    public void Update()
    {
        if (Input.GetKeyDown(KeyCode.G))
        {
            Grab();
        }

        if (Input.GetKeyDown(KeyCode.R))
        {
            Release();
        }
           
    }
}

Oh~ Thank you reply.

So if you use this script, do you mean you can use softbody even if you grab an object?

Then let me ask you one more question.

Where should I put that script? I'd like to know that.
Reply
#4
(17-06-2020, 10:42 AM)JeongMin Lee Wrote: Oh~ Thank you reply.

So if you use this script, do you mean you can use softbody even if you grab an object?

You can't grab a softbody (or any deformable object) by parenting it under a transform, for the reasons I mentioned before. This script grabs a softbody, by doing what I explained in the previous message:

Quote:detects which particles are in contact with a collider/trigger, then fix them by setting their mass to infinite and set their position relative to the collider.



(17-06-2020, 10:42 AM)JeongMin Lee Wrote: Then let me ask you one more question.

Where should I put that script? I'd like to know that.

The script starts with [RequireComponent(typeof(ObiCollider))], so you should place it in the collider that is going to grab the softbody (the player's hand, for example).
Reply