Scripting constraints

Changing constraint parameters

You can modify constraint parameters at runtime. To do this, you just need to grab the constraint component (either directly from the GameObject, or trough the accesor properties in ObiActor), modify the desired property, and call its PushDataToSolver() method.

Here's a small example: a component that sets distance constraint elasticity to 50% upon pressing any key:

using UnityEngine;
using Obi;

[RequireComponent(typeof(ObiDistanceConstraints))]
public class ElasticActor : MonoBehaviour {

	void Update () {

		if (Input.anyKeyDown){
			ObiDistanceConstraints constraints = GetComponent<ObiDistanceConstraints>();
			constraints.stiffness = 0.5f;
			constraints.PushDataToSolver();
		}
	}
}
			

Adding and removing constraints

Here, we only changed the stiffness of existing constraints. However you can also add/remove constraints. To do so, you'll need to access one of the constraint batches (there's always only one, except in the case of ObiStitcher) to add/remove constraints to/from it. Also, you'll need to remove the constraints from the solver before adding or removing constraints, and add them back once you're done.

using UnityEngine;
using Obi;

[RequireComponent(typeof(ObiDistanceConstraints))]
public class AddConstraints : MonoBehaviour {

	void Update () {

		if (Input.anyKeyDown){

			ObiDistanceConstraints constraints = GetComponent<ObiDistanceConstraints>();
			ObiDistanceConstraintBatch batch = (ObiDistanceConstraintBatch)constraints.GetFirstBatch();

			// remove the constraints from the solver, because we cannot modify the constraints list while the solver is using it.
			constraints.RemoveFromSolver(null);

			// remove the first constraint.
			batch.RemoveConstraint(0);

			// add all constraints back:
			constraints.AddToSolver(null);
		}
	}
}
			

Tearing rope and cloth

Both ObiRope and ObiTearableCloth have a Tear(constraintIndex) method that allows you to tear a distance constraint. Tearing is an operation that usually involves altering, adding and/or removing constraints. Because of this, you need to remove the constraints from the solver before calling it and adding them back after you're done.

Also, new particles are activated when you call Tear(). The solver must be made aware of this by calling UpdateActiveParticles() afterwards. And in the case of cloth, you must also tell it to update its topology with a call to UpdateDeformableTriangles(). By taking care of manually calling these methods, the cost of updating constraints and particles can be amortized over multiple calls to Tear().

This is what the Tear() method does internally:

  • Determine which of the two particles joined by the distance constraint has higher mass.
  • Create a duplicate of said particle, and halve both (original and copy) particle's masses.
  • Update the constraints at the other side of the tear, so that they reference the new particle.
  • Update mesh topology, remove constraints and generate new constraints where needed (cloth only).
using UnityEngine;
using Obi;

[RequireComponent(typeof(ObiRope))]
public class RopeCutter : MonoBehaviour {

	void Update () {

		if (Input.anyKeyDown){

			ObiRope rope = GetComponent<ObiRope>();

			// remove constraints from solver:
			rope.DistanceConstraints.RemoveFromSolver(null);
			rope.BendingConstraints.RemoveFromSolver(null);

			rope.Tear(5); // tear the fifth distance constraint.
			// you could call Tear() as many times as you wish here.

			// add constraints to solver
			rope.BendingConstraints.AddToSolver(rope);
			rope.DistanceConstraints.AddToSolver(rope);

			// update active bending constraints:
			rope.BendingConstraints.SetActiveConstraints();

			// only for cloth: commit changes to the topology:
			// UpdateDeformableTriangles();

			// upload active particle list to solver:
			rope.Solver.UpdateActiveParticles();
		}
	}
}