//---------------------------------------------- // Realistic Car Controller // // Copyright © 2014 - 2023 BoneCracker Games // https://www.bonecrackergames.com // Buğra Özdoğanlar // //---------------------------------------------- using System.Collections; using System.Collections.Generic; using UnityEngine; /// /// Damage class. /// [System.Serializable] public class RCC_Damage { internal RCC_CarControllerV3 carController; // Car controller. public bool automaticInstallation = true; // If set to enabled, all parts of the vehicle will be processed. If disabled, each part can be selected individually. // Mesh deformation [Space()] [Header("Mesh Deformation")] public bool meshDeformation = true; public DeformationMode deformationMode = DeformationMode.Fast; public enum DeformationMode { Accurate, Fast } [Range(1, 100)] public int damageResolution = 100; // Resolution of the deformation. public LayerMask damageFilter = -1; // LayerMask filter. Damage will be taken from the objects with these layers. public float damageRadius = .5f; // Verticies in this radius will be effected on collisions. public float damageMultiplier = 1f; // Damage multiplier. public float maximumDamage = .5f; // Maximum Vert Distance For Limiting Damage. 0 Value Will Disable The Limit. private readonly float minimumCollisionImpulse = .5f; // Minimum collision force. private readonly float minimumVertDistanceForDamagedMesh = .002f; // Comparing Original Vertex Positions Between Last Vertex Positions To Decide Mesh Is Repaired Or Not. public struct OriginalMeshVerts { public Vector3[] meshVerts; } // Struct for Original Mesh Verticies positions. public struct OriginalWheelPos { public Vector3 wheelPosition; public Quaternion wheelRotation; } public struct MeshCol { public Collider col; public bool created; } public OriginalMeshVerts[] originalMeshData; // Array for struct above. public OriginalMeshVerts[] damagedMeshData; // Array for struct above. public OriginalWheelPos[] originalWheelData; // Array for struct above. public OriginalWheelPos[] damagedWheelData; // Array for struct above. [Space()] [HideInInspector] public bool repairNow = false; // Repairing now. [HideInInspector] public bool repaired = true; // Returns true if vehicle is completely repaired. private bool deformingNow = false; // Deforming the mesh now. private bool deformed = true; // Returns true if vehicle is completely deformed. private float deformationTime = 0f; // Timer for deforming the vehicle. [Space()] public bool recalculateNormals = true; // Recalculate normals while deforming / restoring the mesh. public bool recalculateBounds = true; // Recalculate bounds while deforming / restoring the mesh. // Wheel deformation [Space()] [Header("Wheel Deformation")] public bool wheelDamage = true; // Use wheel damage. public float wheelDamageRadius = .5f; // Wheel damage radius. public float wheelDamageMultiplier = 1f; // Wheel damage multiplier. public bool wheelDetachment = true; // Use wheel detachment. // Light deformation [Space()] [Header("Light Deformation")] public bool lightDamage = true; // Use light damage. public float lightDamageRadius = .5f; //Light damage radius. public float lightDamageMultiplier = 1f; //Light damage multiplier. // Part deformation [Space()] [Header("Part Deformation")] public bool partDamage = true; // Use part damage. public float partDamageRadius = .5f; //Light damage radius. public float partDamageMultiplier = 1f; //Light damage multiplier. [Space()] public MeshFilter[] meshFilters; // Collected mesh filters. public RCC_DetachablePart[] detachableParts; // Collected detachable parts. public RCC_Light[] lights; // Collected lights. public RCC_WheelCollider[] wheels; // Collected wheels. private Vector3 contactPoint = Vector3.zero; private Vector3[] contactPoints; /// /// Collecting all meshes and detachable parts of the vehicle. /// public void Initialize(RCC_CarControllerV3 _carController) { // Getting the main car controller. carController = _carController; if (automaticInstallation) { if (meshDeformation) { MeshFilter[] allMeshFilters = carController.gameObject.GetComponentsInChildren(true); List properMeshFilters = new List(); // Model import must be readable. If it's not readable, inform the developer. We don't wanna deform wheel meshes. Exclude any meshes belongs to the wheels. foreach (MeshFilter mf in allMeshFilters) { if (mf.mesh != null) { if (!mf.mesh.isReadable) Debug.LogError("Not deformable mesh detected. Mesh of the " + mf.transform.name + " isReadable is false; Read/Write must be enabled in import settings for this model!"); else if (!mf.transform.IsChildOf(carController.FrontLeftWheelTransform) && !mf.transform.IsChildOf(carController.FrontRightWheelTransform) && !mf.transform.IsChildOf(carController.RearLeftWheelTransform) && !mf.transform.IsChildOf(carController.RearRightWheelTransform)) properMeshFilters.Add(mf); } } GetMeshes(properMeshFilters.ToArray()); } if (lightDamage) GetLights(carController.GetComponentsInChildren()); if (partDamage) GetParts(carController.GetComponentsInChildren()); if (wheelDamage) GetWheels(carController.GetComponentsInChildren()); } } /// /// Gets all meshes. /// /// public void GetMeshes(MeshFilter[] allMeshFilters) { meshFilters = allMeshFilters; } /// /// Gets all lights. /// /// public void GetLights(RCC_Light[] allLights) { lights = allLights; } /// /// Gets all detachable parts. /// /// public void GetParts(RCC_DetachablePart[] allParts) { detachableParts = allParts; } /// /// Gets all wheels /// /// public void GetWheels(RCC_WheelCollider[] allWheels) { wheels = allWheels; } /// /// We will be using two structs for deformed sections. Original part struction, and deformed part struction. /// All damaged meshes and wheel transforms will be using these structs. At this section, we're creating them with original struction. /// //private void CheckMeshData() { // originalMeshData = new OriginalMeshVerts[meshFilters.Length]; // for (int i = 0; i < meshFilters.Length; i++) // originalMeshData[i].meshVerts = meshFilters[i].mesh.vertices; // damagedMeshData = new OriginalMeshVerts[meshFilters.Length]; // for (int i = 0; i < meshFilters.Length; i++) // damagedMeshData[i].meshVerts = meshFilters[i].mesh.vertices; //} /// /// We will be using two structs for deformed sections. Original part struction, and deformed part struction. /// All damaged meshes and wheel transforms will be using these structs. At this section, we're creating them with original struction. /// private void CheckWheelData() { originalWheelData = new OriginalWheelPos[wheels.Length]; for (int i = 0; i < wheels.Length; i++) { originalWheelData[i].wheelPosition = wheels[i].transform.localPosition; originalWheelData[i].wheelRotation = wheels[i].transform.localRotation; } damagedWheelData = new OriginalWheelPos[wheels.Length]; for (int i = 0; i < wheels.Length; i++) { damagedWheelData[i].wheelPosition = wheels[i].transform.localPosition; damagedWheelData[i].wheelRotation = wheels[i].transform.localRotation; } } /// /// Moving deformed vertices to their original positions while repairing. /// public void UpdateRepair() { if (!carController) return; // If vehicle is not repaired completely, and repairNow is enabled, restore all deformed meshes to their original structions. if (!repaired && repairNow) { //if (originalMeshData == null || originalMeshData.Length < 1) // CheckMeshData(); int k; repaired = true; // If deformable mesh is still exists, get all verticies of the mesh first. And then move all single verticies to the original positions. If verticies are close enough to the original // position, repaired = true; for (k = 0; k < meshFilters.Length; k++) { if (meshFilters[k] != null && meshFilters[k].mesh != null) { // Get all verticies of the mesh first. Vector3[] vertices = meshFilters[k].mesh.vertices; for (int i = 0; i < vertices.Length; i++) { // And then move all single verticies to the original positions if (deformationMode == DeformationMode.Accurate) vertices[i] += (originalMeshData[k].meshVerts[i] - vertices[i]) * (Time.deltaTime * 5f); else vertices[i] += (originalMeshData[k].meshVerts[i] - vertices[i]); // If verticies are close enough to their original positions, repaired = true; if ((originalMeshData[k].meshVerts[i] - vertices[i]).magnitude >= minimumVertDistanceForDamagedMesh) repaired = false; } // We were using the variable named "vertices" above, therefore we need to set the new verticies to the damaged mesh data. // Damaged mesh data also restored while repairing with this proccess. damagedMeshData[k].meshVerts = vertices; // Setting new verticies to the all meshes. Recalculating normals and bounds, and then optimizing. This proccess can be heavy for high poly meshes. // You may want to disable last three lines. meshFilters[k].mesh.SetVertices(vertices); if (recalculateNormals) meshFilters[k].mesh.RecalculateNormals(); if (recalculateBounds) meshFilters[k].mesh.RecalculateBounds(); } } for (k = 0; k < wheels.Length; k++) { if (wheels[k] != null) { // Get all verticies of the mesh first. Vector3 wheelPos = wheels[k].transform.localPosition; // And then move all single verticies to the original positions if (deformationMode == DeformationMode.Accurate) wheelPos += (originalWheelData[k].wheelPosition - wheelPos) * (Time.deltaTime * 5f); else wheelPos += (originalWheelData[k].wheelPosition - wheelPos); // If verticies are close enough to their original positions, repaired = true; if ((originalWheelData[k].wheelPosition - wheelPos).magnitude >= minimumVertDistanceForDamagedMesh) repaired = false; // We were using the variable named "vertices" above, therefore we need to set the new verticies to the damaged mesh data. // Damaged mesh data also restored while repairing with this proccess. damagedWheelData[k].wheelPosition = wheelPos; wheels[k].transform.localPosition = wheelPos; wheels[k].transform.localRotation = Quaternion.identity; if (!wheels[k].gameObject.activeSelf) wheels[k].gameObject.SetActive(true); carController.ESPBroken = false; wheels[k].Inflate(); } } // Repairing and restoring all detachable parts of the vehicle. for (int i = 0; i < detachableParts.Length; i++) { if (detachableParts[i] != null) detachableParts[i].OnRepair(); } // Repairing and restoring all lights of the vehicle. for (int i = 0; i < lights.Length; i++) { if (lights[i] != null) lights[i].OnRepair(); } // If all meshes are completely restored, make sure repairing now is false. if (repaired) repairNow = false; } } /// /// Moving vertices of the collided meshes to the damaged positions while deforming. /// public void UpdateDamage() { if (!carController) return; //if (originalMeshData == null || originalMeshData.Length < 1) // CheckMeshData(); // If vehicle is not deformed completely, and deforming is enabled, deform all meshes to their damaged structions. if (!deformed && deformingNow) { int k; deformed = true; deformationTime += Time.deltaTime; // If deformable mesh is still exists, get all verticies of the mesh first. And then move all single verticies to the damaged positions. If verticies are close enough to the original // position, deformed = true; for (k = 0; k < meshFilters.Length; k++) { if (meshFilters[k] != null && meshFilters[k].mesh != null) { // Get all verticies of the mesh first. Vector3[] vertices = meshFilters[k].mesh.vertices; // And then move all single verticies to the damaged positions. for (int i = 0; i < vertices.Length; i++) { if (deformationMode == DeformationMode.Accurate) vertices[i] += (damagedMeshData[k].meshVerts[i] - vertices[i]) * (Time.deltaTime * 5f); else vertices[i] += (damagedMeshData[k].meshVerts[i] - vertices[i]); } // Setting new verticies to the all meshes. Recalculating normals and bounds, and then optimizing. This proccess can be heavy for high poly meshes. meshFilters[k].mesh.SetVertices(vertices); if (recalculateNormals) meshFilters[k].mesh.RecalculateNormals(); if (recalculateBounds) meshFilters[k].mesh.RecalculateBounds(); } } for (k = 0; k < wheels.Length; k++) { if (wheels[k] != null) { Vector3 vertices = wheels[k].transform.localPosition; if (deformationMode == DeformationMode.Accurate) vertices += (damagedWheelData[k].wheelPosition - vertices) * (Time.deltaTime * 5f); else vertices += (damagedWheelData[k].wheelPosition - vertices); wheels[k].transform.localPosition = vertices; //wheels[k].transform.localRotation = Quaternion.Euler(vertices); } } // Make sure deforming proccess takes only 1 second. if (deformationMode == DeformationMode.Accurate && deformationTime <= 1f) deformed = false; // If all meshes are completely deformed, make sure deforming is false and timer is set to 0. if (deformed) { deformingNow = false; deformationTime = 0f; } } } /// /// Deforming meshes. /// /// /// private void DamageMesh(float impulse) { if (!carController) return; //if (originalMeshData == null || originalMeshData.Length < 1) // CheckMeshData(); // We will be checking all mesh filters with these contact points. If contact point is close enough to the mesh, deformation will be applied. for (int i = 0; i < meshFilters.Length; i++) { // If mesh filter is not null, enabled, and has a valid mesh data... if (meshFilters[i] != null && meshFilters[i].mesh != null && meshFilters[i].gameObject.activeSelf) { // Getting closest point to the mesh. Distance value will be set to closest point of the mesh - contact point. float distance = Vector3.Distance(NearestVertex(meshFilters[i].transform, meshFilters[i], contactPoint), contactPoint); // If distance between contact point and closest point of the mesh is in range... if (distance <= damageRadius) { // Collision direction. Vector3 collisionDirection = contactPoint - carController.transform.position; collisionDirection = -collisionDirection.normalized; // All vertices of the mesh. Vector3[] vertices = damagedMeshData[i].meshVerts; for (int k = 0; k < vertices.Length; k++) { // Contact point is a world space unit. We need to transform to the local space unit with mesh origin. Verticies are local space units. Vector3 point = meshFilters[i].transform.InverseTransformPoint(contactPoint); // Distance between vertex and contact point. float distanceToVert = (point - vertices[k]).magnitude; // If distance between vertex and contact point is in range... if (distanceToVert <= damageRadius) { // Default impulse of the collision. float damage = impulse; // The damage should decrease with distance from the contact point. damage -= damage * Mathf.Clamp01(distanceToVert / damageRadius); Quaternion rot = Quaternion.identity; Vector3 vW = carController.transform.TransformPoint(vertices[k]); vW += rot * (collisionDirection * damage * (damageMultiplier / 10f)); vertices[k] = carController.transform.InverseTransformPoint(vW); // If distance between original vertex position and deformed vertex position exceeds limits, make sure they are in the limits. if (maximumDamage > 0 && ((vertices[k] - originalMeshData[i].meshVerts[k]).magnitude) > maximumDamage) vertices[k] = originalMeshData[i].meshVerts[k] + (vertices[k] - originalMeshData[i].meshVerts[k]).normalized * (maximumDamage); } } } } } } /// /// Deforming wheels. Actually changing their local positions and rotations based on the impact. /// /// /// private void DamageWheel(float impulse) { if (!carController) return; if (originalWheelData == null || originalWheelData.Length < 1) CheckWheelData(); for (int i = 0; i < wheels.Length; i++) { if (wheels[i] != null && wheels[i].gameObject.activeSelf) { Vector3 wheelPos = damagedWheelData[i].wheelPosition; Vector3 collisionDirection = contactPoint - carController.transform.position; collisionDirection = -collisionDirection.normalized; Vector3 closestPoint = wheels[i].WheelCollider.ClosestPointOnBounds(contactPoint); float distance = Vector3.Distance(closestPoint, contactPoint); if (distance < wheelDamageRadius) { float damage = (impulse * wheelDamageMultiplier) / 30f; // The damage should decrease with distance from the contact point. damage -= damage * Mathf.Clamp01(distance / wheelDamageRadius); Vector3 vW = carController.transform.TransformPoint(wheelPos); vW += (collisionDirection * damage); wheelPos = carController.transform.InverseTransformPoint(vW); if (maximumDamage > 0 && ((wheelPos - originalWheelData[i].wheelPosition).magnitude) > maximumDamage) { //wheelPos = originalWheelData[i].wheelPosition + (wheelPos - originalWheelData[i].wheelPosition).normalized * (maximumDamage); if (wheelDetachment && wheels[i].gameObject.activeSelf) DetachWheel(wheels[i]); } damagedWheelData[i].wheelPosition = wheelPos; } } } } /// /// Deforming the detachable parts. /// /// /// private void DamagePart(float impulse) { if (!carController) return; if (detachableParts != null && detachableParts.Length >= 1) { for (int i = 0; i < detachableParts.Length; i++) { if (detachableParts[i] != null && detachableParts[i].gameObject.activeSelf) { if (detachableParts[i].partCollider != null) { Vector3 closestPoint = detachableParts[i].partCollider.ClosestPointOnBounds(contactPoint); float distance = Vector3.Distance(closestPoint, contactPoint); float damage = impulse * partDamageMultiplier; // The damage should decrease with distance from the contact point. damage -= damage * Mathf.Clamp01(distance / damageRadius); if (distance <= damageRadius) detachableParts[i].OnCollision(damage); } else { if ((contactPoint - detachableParts[i].transform.position).magnitude < 1f) detachableParts[i].OnCollision(impulse); } } } } } /// /// Deforming the lights. /// /// /// private void DamageLight(float impulse) { if (!carController) return; if (lights != null && lights.Length >= 1) { for (int i = 0; i < lights.Length; i++) { if (lights[i] != null && lights[i].gameObject.activeSelf) { if ((contactPoint - lights[i].transform.position).magnitude < lightDamageRadius) lights[i].OnCollision(impulse * lightDamageMultiplier); } } } } /// /// Detaches the target wheel. /// /// public void DetachWheel(RCC_WheelCollider wheelCollider) { if (!carController) return; if (!wheelCollider) return; if (!wheelCollider.gameObject.activeSelf) return; wheelCollider.gameObject.SetActive(false); Transform wheelModel = wheelCollider.wheelModel; GameObject clonedWheel = GameObject.Instantiate(wheelModel.gameObject, wheelModel.transform.position, wheelModel.transform.rotation, null); clonedWheel.SetActive(true); clonedWheel.AddComponent(); GameObject clonedMeshCollider = new GameObject("Mesh Collider"); clonedMeshCollider.transform.SetParent(clonedWheel.transform, false); clonedMeshCollider.transform.position = RCC_GetBounds.GetBoundsCenter(clonedWheel.transform); MeshCollider mc = clonedMeshCollider.AddComponent(); MeshFilter biggestMesh = RCC_GetBounds.GetBiggestMesh(clonedWheel.transform); mc.sharedMesh = biggestMesh.mesh; mc.convex = true; carController.ESPBroken = true; } /// /// Raises the collision enter event. /// /// Collision. public void OnCollision(Collision collision) { if (!carController) return; if (!carController.useDamage) return; if (((1 << collision.gameObject.layer) & damageFilter) != 0) { float impulse = collision.impulse.magnitude / 10000f; if (collision.rigidbody) impulse *= collision.rigidbody.mass / 1000f; if (impulse < minimumCollisionImpulse) impulse = 0f; if (impulse > 10f) impulse = 10f; if (impulse > 0f) { deformingNow = true; deformed = false; repairNow = false; repaired = false; // First, we are getting all contact points. ContactPoint[] contacts = collision.contacts; contactPoints = new Vector3[contacts.Length]; for (int i = 0; i < contactPoints.Length; i++) contactPoints[i] = contacts[i].point; contactPoint = ContactPointsMagnitude(); if (meshFilters != null && meshFilters.Length >= 1 && meshDeformation) DamageMesh(impulse); if (wheels != null && wheels.Length >= 1 && wheelDamage) DamageWheel(impulse); if (detachableParts != null && detachableParts.Length >= 1 && partDamage) DamagePart(impulse); if (lights != null && lights.Length >= 1 && lightDamage) DamageLight(impulse); } } } /// /// Raises the collision enter event. /// /// Collision. public void OnCollisionWithRay(RaycastHit hit, float impulse) { if (!carController) return; if (!carController.useDamage) return; if (impulse < minimumCollisionImpulse) impulse = 0f; if (impulse > 10f) impulse = 10f; if (impulse > 0f) { deformingNow = true; deformed = false; repairNow = false; repaired = false; // First, we are getting all contact points. contactPoint = hit.point; if (meshFilters != null && meshFilters.Length >= 1 && meshDeformation) DamageMesh(impulse); if (wheels != null && wheels.Length >= 1 && wheelDamage) DamageWheel(impulse); if (detachableParts != null && detachableParts.Length >= 1 && partDamage) DamagePart(impulse); if (lights != null && lights.Length >= 1 && lightDamage) DamageLight(impulse); } } private Vector3 ContactPointsMagnitude() { Vector3 magnitude = Vector3.zero; for (int i = 0; i < contactPoints.Length; i++) magnitude += contactPoints[i]; magnitude /= contactPoints.Length; return magnitude; } /// /// Finds closest vertex to the target point. /// /// /// /// /// public static Vector3 NearestVertex(Transform trans, MeshFilter mf, Vector3 point) { // Convert point to local space. point = trans.InverseTransformPoint(point); float minDistanceSqr = Mathf.Infinity; Vector3 nearestVertex = Vector3.zero; // Check all vertices to find nearest. foreach (Vector3 vertex in mf.mesh.vertices) { Vector3 diff = point - vertex; float distSqr = diff.sqrMagnitude; if (distSqr < minDistanceSqr) { minDistanceSqr = distSqr; nearestVertex = vertex; } } // Convert nearest vertex back to the world space. return trans.TransformPoint(nearestVertex); } }