Created
December 4, 2020 23:09
-
-
Save marcospgp/b0394c601a90d9ecfef0ea2457f2e228 to your computer and use it in GitHub Desktop.
Unity C# script that jellifies a cube.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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