03-05-2021, 10:49 AM
(This post was last modified: 03-05-2021, 10:56 AM by josemendez.)
(03-05-2021, 06:16 AM)ink_13 Wrote: Hey, I've had a look over previous forum questions and have seen some answers that i think are related to an older system with pins...
I have a rope that i'm at runtime I'm wanting to be able to grab... Previously it seemed like you would do this by creating a pin at the point where your hand touched the rope...
My current approach is to try and find the closest particle to where your hand is, create a particle group at that point, and use a particle attachment to make a connection between that point and the transform of your hand.
I'm abit stuck though, i can't figure out how to make a particle group at runtime, nor figure out how to specify the location of where the particle group should be. I have a convoluted idea that I could create a particle collision and see where the collision point is...
Very curious if someone has a suggestion on if I'm approaching this in the right direction, and if i am - how to create a particle group at runtime at the point of collision...
#1) you need to determine whether you need two-way coupling at all, or maybe one-way coupling is enough. That is, if the rope should be able to apply forces to the object it is attached to (two-way coupling) or not (one-way coupling).
Two-way coupling is done by adding pin constraints, one-way coupling done by setting the particle mass to infinite and overriding its position.
It has been this way since Obi 1.0. In newer versions, dynamic and static attachments (respectively) were added as a higher-level layer that automatically manage this for you.
If all you want is to grab the rope and move it around, one-way coupling is enough.
#2) you need to consider whether you want to keep track of the attached particles yourself, or if you want to delegate this task to attachments. When dealing with collisions and individual particles, working with particles directly is often easier since you don't have to create particle groups or attachments at all.
So all you need to do is keep track of which particles are in contact with a collider, then set their inverse mass to zero (same as setting the mass to infinite, see: http://obi.virtualmethodstudio.com/tutor...icles.html), and set their position to whatever value you want while they're grabbed. To release them, just revert their inverse mass to whatever it was.
Obi 6 includes a sample script (ObiCollisionGrabber) that does just this. See:
Code:
using UnityEngine;
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 ObiContactGrabber : 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 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<ObiActor> grabbedActors = new HashSet<ObiActor>(); /**< set of softbodies grabbed during this step.*/
private Matrix4x4 grabber2Solver;
private Matrix4x4 solver2Grabber;
private void Awake()
{
localCollider = GetComponent<ObiCollider>();
}
private void OnEnable()
{
if (solver != null)
solver.OnCollision += Solver_OnCollision;
}
private void OnDisable()
{
if (solver != null)
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 UpdateParticleProperties()
{
// Update rest shape matching of all grabbed softbodies:
foreach (ObiActor actor in grabbedActors)
{
actor.UpdateParticleProperties();
}
}
/**
* 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()
{
grabbedActors.Clear();
var world = ObiColliderWorld.GetInstance();
if (solver != null && collisionEvent != null)
{
foreach (Oni.Contact contact in collisionEvent.contacts)
{
// this one is an actual collision:
if (contact.distance < 0.01f)
{
var contactCollider = world.colliderHandles[contact.other].owner;
// if the current contact references our collider, proceed to grab the particle.
if (contactCollider == localCollider)
{
// try to grab the particle, if not already grabbed.
if (GrabParticle(contact.particle))
grabbedActors.Add(solver.particleToActor[contact.particle].actor);
}
}
}
}
UpdateParticleProperties();
}
/**
* 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;
UpdateParticleProperties();
grabbedActors.Clear();
grabbedParticles.Clear();
}
/**
* Updates the position of the grabbed particles.
*/
private void FixedUpdate()
{
foreach (GrabbedParticle p in grabbedParticles)
solver.positions[p.index] = grabber2Solver.MultiplyPoint3x4(p.localPosition);
}
}