Skip to content

Instantly share code, notes, and snippets.

@marcospgp
Created December 4, 2020 23:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marcospgp/b0394c601a90d9ecfef0ea2457f2e228 to your computer and use it in GitHub Desktop.
Save marcospgp/b0394c601a90d9ecfef0ea2457f2e228 to your computer and use it in GitHub Desktop.
Unity C# script that jellifies a cube.
using System.Linq;
using UnityEngine;
[RequireComponent(typeof(MeshFilter))]
public class Jellify : MonoBehaviour {
[SerializeField]
[Tooltip("The object's resistance to outside forces. Default value is 10.")]
[Range(1f, 1000f)]
private float stiffness = 10f;
[SerializeField]
[Tooltip("The object's internal cohesion. Default value is 100.")]
[Range(1f, 1000f)]
private float cohesion = 100f;
[SerializeField]
[Tooltip("How fast forces are absorbed by the object, in % of velocity per second. Default value is 10.")]
[Range(1f, 100f)]
private float decay = 10f;
private Vector3[] vertexVelocities;
private Vector3[] originalVertices; // Original vertex positions
private GameObject[] vertexIndicators; // Spheres indicating where vertices are
void Start() {
originalVertices = GetComponent<MeshFilter>().mesh.vertices;
vertexVelocities = new Vector3[originalVertices.Length];
// Display vertices in-game
// HighlightVertices();
}
void FixedUpdate() {
DisplaceVertices(Time.fixedDeltaTime);
Rebound(Time.fixedDeltaTime);
Decay(Time.deltaTime);
Vector3[] vertices = GetComponent<MeshFilter>().mesh.vertices;
// Draw vertex velocities
// for (int i = 0; i < vertices.Length; i++) {
// Debug.DrawRay(
// transform.TransformPoint(vertices[i]),
// vertexVelocities[i],
// Color.white
// );
// }
// Update vertex indicator positions
// for (int i = 0; i < vertexIndicators.Length; i++) {
// vertexIndicators[i].transform.localPosition = vertices[i];
// }
}
/// <summary>
/// Places a sphere on every vertex of the mesh, so that their displacement
/// is visible in-game.
/// </summary>
void HighlightVertices() {
Vector3[] vertices = GetComponent<MeshFilter>().mesh.vertices;
Debug.Log($"Mesh has {vertices.Length} vertices.");
vertexIndicators = new GameObject[vertices.Length];
for (int i = 0; i < vertices.Length; i++) {
GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
// Remove collider
Destroy(sphere.GetComponent<SphereCollider>());
// Change color, position, & scale
sphere.GetComponent<MeshRenderer>().material.color = Color.cyan;
sphere.transform.localPosition = vertices[i];
sphere.transform.localScale = sphere.transform.localScale * 0.03f;
// Make sphere a child of this gameobject
sphere.transform.SetParent(transform, worldPositionStays: false);
vertexIndicators[i] = sphere;
}
}
void OnCollisionEnter(Collision collision) {
HandleCollision(collision);
}
void OnCollisionStay(Collision collision) {
HandleCollision(collision);
}
// List<GameObject> contactIndicators = new List<GameObject>();
void HandleCollision(Collision collision) {
// Clear previous collision indicators
// foreach (GameObject x in contactIndicators) {
// Destroy(x);
// }
// contactIndicators.Clear();
// Debug.Log($"Collision! Generated an impulse of {collision.impulse} (shown in red at object center)");
// Debug.DrawRay(transform.position, collision.impulse, Color.red);
Vector3[] vertices = GetComponent<MeshFilter>().mesh.vertices;
Vector3[] normals = GetComponent<MeshFilter>().mesh.normals;
// Debug.Log($"Collision impulse: {collision.impulse}");
for (int i = 0; i < vertices.Length; i++) {
Vector3 vertexWorldPos = transform.TransformPoint(vertices[i]);
// Get vertex distance to closest contact point
float distance = collision.contacts
.Select(contact =>
Vector3.Distance(vertexWorldPos, contact.point))
.Min();
// Jellification happens by giving vertices some inertia on collision,
// relative to how far from the collision they are.
Vector3 vertexImpulse =
-1 * collision.impulse * (1 - (1 / (distance + 1))) / stiffness;
// Debug.Log($"Vertex impulse: {vertexImpulse}");
vertexVelocities[i] += vertexImpulse;
}
// Draw collision indicator
// foreach (ContactPoint c in collision.contacts) {
// GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
// // Remove collider
// Destroy(sphere.GetComponent<SphereCollider>());
// // Change color, position, & scale
// sphere.GetComponent<MeshRenderer>().material.color = Color.red;
// sphere.transform.position = c.point;
// sphere.transform.localScale = sphere.transform.localScale * 0.03f;
// // Make sphere a child of this gameobject
// sphere.transform.SetParent(transform, worldPositionStays: true);
// contactIndicators.Add(sphere);
// }
}
void DisplaceVertices(float deltaTime) {
Mesh mesh = GetComponent<MeshFilter>().mesh;
Vector3[] vertices = mesh.vertices;
vertices = vertices.Select((x, i) => x + vertexVelocities[i] * deltaTime).ToArray();
mesh.vertices = vertices;
// Update collider
GetComponent<MeshCollider>().sharedMesh = mesh;
}
void Rebound(float deltaTime) {
// A material's stiffness is a constant force pulling it back towards
// its original position.
// Assuming the force scales linearly with the object's mass, we apply
// a constant velocity.
// There's no need to ensure mesh seams stick together, since the
// rebound is only dependent on a vertex's position.
Mesh mesh = GetComponent<MeshFilter>().mesh;
Vector3[] vertices = mesh.vertices;
for (int i = 0; i < vertices.Length; i++) {
Vector3 difference = originalVertices[i] - vertices[i];
float distance = difference.magnitude;
Vector3 direction = difference.normalized;
// Rebound scales quadratically with distance
Vector3 rebound =
direction * cohesion * Mathf.Pow(distance, 2) * deltaTime;
vertexVelocities[i] += rebound;
}
mesh.vertices = vertices;
}
void Decay(float deltaTime) {
vertexVelocities = vertexVelocities.Select(v => {
// Decay vertex velocity x% per second
float x = decay;
return v * (1 - (x * deltaTime));
}).ToArray();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment