Mesh deformation on scaled object strange result

Hey guys, i have been struggling to figure out my issue with mesh deformation on collision for hours now and I don’t get it. I have two rigid body spheres dropping on cars, i move each vertex on the cars in the direction of the collision normal within a certain radius. Looks great on the red car I found on the asset store but the other car that an acquaintance I’m doing a project with just look messed up. I believe this has something to do with that the red car has scale 1 and the black car has lots of different scales on th different parts. But I can’t really figure out how to handle this correctly. I would really appreciate if someone with better understanding of how to handle this could help me figure this out.

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Assets.Scripts
{
    public class ImpulseMeshDeformer : MonoBehaviour
    {
        private float _deformScalar = 0.1f;

        // Only deform vertices that are within this distance from the collision
        private float maxVertexCollisionPointDistance = 0.5f;
        // List of all mesh filters who's mesh we want to deform
        [SerializeField] private List<MeshFilter> _meshFilters;

        [SerializeField] private float _minimumImpulseToCauseDeformation = 50f;
        // List of vertices in the mesh as it was before applying damage
        private List<Vector3[]> originalMeshData;

        // List of vertices in the damaged mesh
        List<Vector3[]> damagedMeshData;

        // Preallocate memory to hold contact points to reduce the need for memory allocation and garbage 
       //  at runtime
        private readonly List<ContactPoint> _collisionContacts = new List<ContactPoint>(50);

        // Only allow a vertice to be moved this far from its original point
        private float _maxDeformOffset = 0.5f;
       
        void Start()
        {
            if (_meshFilters.Count == 0)
                _meshFilters = GetComponentsInChildren<MeshFilter>(true).ToList();

            // Initialize a list of original

            originalMeshData = new List<Vector3[]>(_meshFilters.Count);
            damagedMeshData = new List<Vector3[]>(_meshFilters.Count);

            // We need a original list of vertices for each mesh filter as well as a damaged list
            // Loop trough the mesh filters and copy their vertice data to our damaged and original mesh 
           // struct / class
            foreach (var meshFilter in _meshFilters)
            {
                originalMeshData.Add(meshFilter.mesh.vertices);
                damagedMeshData.Add(meshFilter.mesh.vertices);
            }
        }
        public void OnCollisionEnter(Collision collision) => DeformMeshes(collision);

        void DeformMeshes(Collision collision)
        {
            int nrOfContactPoints = collision.GetContacts(_collisionContacts);
            if (collision.impulse.magnitude < _minimumImpulseToCauseDeformation)
                return;

            Debug.Log("Impulse mesh magnitude: " + collision.impulse.magnitude);
            // Loop trough the list of all mesh filters in the vehicle.
            for (var meshNr = 0; meshNr < _meshFilters.Count; meshNr++)
            {
                var meshFilter = _meshFilters[meshNr];
                // Skip inactive / empty mesh filters
                if (meshFilter.mesh != null && !meshFilter.gameObject.activeSelf)
                    continue;

                // Loop trough all contact points of the collision.
                for (var index = 0; index < nrOfContactPoints; index++)
                {
                    var collisionContact = _collisionContacts[index];

                    // Calculate the collision direction in local coordinate system of the car.
                    Vector3 localDirection =  meshFilter.transform
                                                           .InverseTransformDirection(collisionContact.normal);

                    // Change the collision point to local coordinates of the meshs object.
                    Vector3 localContactPoint =           
                                            meshFilter.transform.InverseTransformPoint(collisionContact.point);                   
                   
                    // Calculate the amount meshes should be deformed by taking the local collision direction
                    // and scaling
                    // it with the impulse and then multiplying it with scaling modifier.
                    var deformVector = localDirection * _deformScalar * collision.impulse.magnitude;

                    // Attempt to adapt movement to scale but does not seem to help
                    deformVector.x /= meshFilter.transform.localScale.x;
                    deformVector.y /= meshFilter.transform.localScale.y;
                    deformVector.z /= meshFilter.transform.localScale.z;
                    //Debug.Log("Divided with scale " + meshFilter.transform.localScale);
                    // Go trough each vertices in the meshFilters mesh.
                    for (int vertexNr = 0; vertexNr < damagedMeshData[meshNr].Length; vertexNr++)
                    {
                        // If distance between the collision point and the vertex is too far, ignore
                        // this vertex and move on to the next one.
                        if ((localContactPoint - damagedMeshData[meshNr][vertexNr]).magnitude 
                            >  maxVertexCollisionPointDistance)
                                continue;

                        // Modify the vertices of the mesh with the deformVector calculated for this collision point.
                        damagedMeshData[meshNr][vertexNr] += deformVector;

                        // Calculate the distance this vertex has moved compared to when the mesh was 
                        //  undamaged.
                        // If distance to original position is greater than the distance limit
                        Vector3 originalToDamageVertexVector =
                            damagedMeshData[meshNr][vertexNr] - originalMeshData[meshNr][vertexNr];

                       
                        var directionToDamagedVertex = originalToDamageVertexVector.normalized;
                        var distanceToOriginalPoint = originalToDamageVertexVector.magnitude;
                        if (distanceToOriginalPoint > _maxDeformOffset)
                        {
                            // Clamp deform offset to _meshDeformOffset distance
                            damagedMeshData[meshNr][vertexNr] = originalMeshData[meshNr][vertexNr] +
                                                                directionToDamagedVertex * _maxDeformOffset;
                        }
                    }
                }
               
                // Update the mesh with the new vertices. Recalculate normals as well so light is 
                // reflected  correctly.
                _meshFilters[meshNr].mesh.SetVertices(damagedMeshData[meshNr]);
                _meshFilters[meshNr].mesh.RecalculateNormals();
            }
        }
    }
}

Here is a link to a minimal project demonstrating the issue:
https://github.com/endasil/car-colision-comparsion

the script in question car-colision-comparsion/Assets/Scripts/ImpulseMeshDeformer.cs at main · endasil/car-colision-comparsion · GitHub

The only correct way to handle this is:

  • leave ALL Transform scales at unity (1,1,1), except:

  • only put non-unity scales on leaf Transforms, eg those that have no children (or whom have children you are happy to also scale exactly the same)

  • only parent other objects to unity-scaled Transforms

How come? Why is there no other way of solving this? Can you please give me some more details so I can understand the issue?

If you dig into the details of the transform and what it means mathematically you can see the issue.

Here’s a longer discussion:

https://forum.unity.com/threads/weird-mesh-distortion-when-rotating-via-script.119770/

1 Like