Skip to content

Instantly share code, notes, and snippets.

@pppoe252110
Last active March 14, 2025 00:14
Show Gist options
  • Save pppoe252110/ad29970d6f7b6b0d3b15ac2777df9144 to your computer and use it in GitHub Desktop.
Save pppoe252110/ad29970d6f7b6b0d3b15ac2777df9144 to your computer and use it in GitHub Desktop.

SF Mesh Deformer

Getting Started

Prerequisites

  • Unity version 2020.3 or higher.

Setup Instructions

Step 1: Create, Configure, and Prefab a Sphere

  1. Launch Unity and open your project.
  2. In the hierarchy, select Create -> 3D Object -> Sphere to instantiate a standard sphere prefab.
  3. With the sphere selected, add the DemoMeshDeformer and Rigidbody components via the inspector.
  4. Drag the configured sphere from the hierarchy into your project's Assets folder to create a prefab.

Step 2: Set Up the Camera

  1. Locate the main camera within the hierarchy (commonly labeled Main Camera).
  2. Attach the FreeCamera and BallThrow components to it.
  3. Within the BallThrow component settings, assign the previously created sphere prefab.

Step 3: Integrate a Deformable Object

  1. Position the object you intend to deform within the scene.
  2. Apply the DeformableMesh script to this object.
  3. Should you require updating the Mesh Collider, activate the relevant option in the DeformableMesh component.

Important Consideration

Make certain that the model designated for deformation has the Read/Write Enabled property activated in its import settings. This adjustment permits the DeformableMesh script to alter the object's mesh dynamically.

using UnityEngine;
namespace SFMeshDeformer.Demo
{
public class BallThrow : MonoBehaviour
{
[SerializeField] private Rigidbody _ballPrefab;
[SerializeField] private float _throwSpeed = 30f;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
var ball = Instantiate(_ballPrefab, transform.position, Quaternion.identity);
ball.linearVelocity = transform.forward * _throwSpeed;
}
}
}
}
using UnityEngine;
namespace SFMeshDeformer.Core
{
public class DeformableMesh : MonoBehaviour
{
[SerializeField] private bool _updateMeshCollider;
private MeshCollider _meshCollider;
private MeshFilter _meshFilter;
private Mesh _mesh;
private void Reset()
{
if (!GetComponent<MeshFilter>())
{
Debug.LogError("MeshFilter component not found on gameObject!");
}
}
private void Start()
{
if (!_meshFilter)
{
_meshFilter = GetComponent<MeshFilter>();
}
if (_updateMeshCollider && _meshCollider == null)
{
_meshCollider = GetComponent<MeshCollider>();
}
if (_meshFilter)
PrepareMesh();
}
public void PrepareMesh()
{
if (!_meshFilter.sharedMesh.isReadable)
{
Debug.LogError("Mesh not readable!");
return;
}
var oldMesh = _meshFilter.sharedMesh;
_mesh = new Mesh()
{
vertices = oldMesh.vertices,
triangles = oldMesh.triangles,
normals = oldMesh.normals,
tangents = oldMesh.tangents,
bounds = oldMesh.bounds,
uv = oldMesh.uv,
uv2 = oldMesh.uv2,
uv3 = oldMesh.uv3,
uv4 = oldMesh.uv4,
uv5 = oldMesh.uv5,
uv6 = oldMesh.uv6,
uv7 = oldMesh.uv7,
uv8 = oldMesh.uv8,
subMeshCount = oldMesh.subMeshCount,
colors = oldMesh.colors
};
_mesh.MarkDynamic();
_meshFilter.sharedMesh = _mesh;
if (_updateMeshCollider)
_meshCollider.sharedMesh = _mesh;
}
public void Deform(Vector3 position, Vector3 direction, float amount, float radius)
{
var sw = System.Diagnostics.Stopwatch.StartNew();
position = transform.InverseTransformPoint(position);
direction = transform.InverseTransformDirection(direction).normalized;
var verts = _mesh.vertices;
var sqrRadius = radius * radius;
var length = verts.Length;
for (int i = 0; i < length; i++)
{
var sqrDist = (position - verts[i]).sqrMagnitude;
if (sqrDist < sqrRadius)
{
var dist = Mathf.Sqrt(sqrDist);
verts[i] += direction * (1f - (dist / radius)) * amount;
}
}
_mesh.vertices = verts;
_mesh.MarkModified();
if (_updateMeshCollider)
_meshCollider.sharedMesh = _mesh;
sw.Stop();
Debug.Log(sw.ElapsedMilliseconds + "ms");
}
}
}
using UnityEngine;
using SFMeshDeformer.Core;
namespace SFMeshDeformer.Demo
{
public class DemoMeshDeformer : MonoBehaviour
{
[SerializeField] private float _minImpulse = 1f;
[SerializeField] private float _radius = 0.25f;
[SerializeField] private float _impulseRadiusMulitplier = 0.2f;
[SerializeField] private float _deformationMultiplier = 0.01f;
private void OnCollisionEnter(Collision collision)
{
if (collision.impulse.magnitude < _minImpulse)
return;
if (collision.transform.TryGetComponent(out DeformableMesh deformableMesh))
{
var direction = -collision.contacts[0].impulse.normalized;
var position = collision.contacts[0].point;
var hitVelocity = collision.contacts[0].impulse.magnitude * _deformationMultiplier;
var radius = _radius + hitVelocity * _impulseRadiusMulitplier;
deformableMesh.Deform(position, direction, hitVelocity, radius);
}
}
}
}
using UnityEngine;
namespace SFMeshDeformer.Demo
{
public class FreeCamera : MonoBehaviour
{
[SerializeField] private float _speed = 6f;
[SerializeField] private float _mouseSensitivity = 2f;
private void Start()
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
void Update()
{
transform.position += transform.forward * Input.GetAxisRaw("Vertical") * Time.deltaTime * _speed;
transform.position += transform.right * Input.GetAxisRaw("Horizontal") * Time.deltaTime * _speed;
var up = 0;
if (Input.GetKey(KeyCode.Space))
{
up = 1;
}
else if (Input.GetKey(KeyCode.LeftShift))
{
up = -1;
}
transform.position += transform.up * up * Time.deltaTime * _speed;
transform.rotation *= Quaternion.Euler(-Input.GetAxis("Mouse Y") * _mouseSensitivity, 0, 0);
transform.rotation *= Quaternion.Euler(0, Input.GetAxis("Mouse X") * _mouseSensitivity, 0);
transform.rotation = Quaternion.Euler(transform.eulerAngles.x, transform.eulerAngles.y, 0);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment