806 lines
29 KiB
C#

//----------------------------------------------
// 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;
/// <summary>
/// Damage class.
/// </summary>
[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;
/// <summary>
/// Collecting all meshes and detachable parts of the vehicle.
/// </summary>
public void Initialize(RCC_CarControllerV3 _carController) {
// Getting the main car controller.
carController = _carController;
if (automaticInstallation) {
if (meshDeformation) {
MeshFilter[] allMeshFilters = carController.gameObject.GetComponentsInChildren<MeshFilter>(true);
List<MeshFilter> properMeshFilters = new List<MeshFilter>();
// 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<RCC_Light>());
if (partDamage)
GetParts(carController.GetComponentsInChildren<RCC_DetachablePart>());
if (wheelDamage)
GetWheels(carController.GetComponentsInChildren<RCC_WheelCollider>());
}
}
/// <summary>
/// Gets all meshes.
/// </summary>
/// <param name="allMeshFilters"></param>
public void GetMeshes(MeshFilter[] allMeshFilters) {
meshFilters = allMeshFilters;
}
/// <summary>
/// Gets all lights.
/// </summary>
/// <param name="allLights"></param>
public void GetLights(RCC_Light[] allLights) {
lights = allLights;
}
/// <summary>
/// Gets all detachable parts.
/// </summary>
/// <param name="allParts"></param>
public void GetParts(RCC_DetachablePart[] allParts) {
detachableParts = allParts;
}
/// <summary>
/// Gets all wheels
/// </summary>
/// <param name="allWheels"></param>
public void GetWheels(RCC_WheelCollider[] allWheels) {
wheels = allWheels;
}
/// <summary>
/// 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.
/// </summary>
//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;
//}
/// <summary>
/// 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.
/// </summary>
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;
}
}
/// <summary>
/// Moving deformed vertices to their original positions while repairing.
/// </summary>
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;
}
}
/// <summary>
/// Moving vertices of the collided meshes to the damaged positions while deforming.
/// </summary>
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;
}
}
}
/// <summary>
/// Deforming meshes.
/// </summary>
/// <param name="collision"></param>
/// <param name="impulse"></param>
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);
}
}
}
}
}
}
/// <summary>
/// Deforming wheels. Actually changing their local positions and rotations based on the impact.
/// </summary>
/// <param name="collision"></param>
/// <param name="impulse"></param>
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;
}
}
}
}
/// <summary>
/// Deforming the detachable parts.
/// </summary>
/// <param name="collision"></param>
/// <param name="impulse"></param>
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);
}
}
}
}
}
/// <summary>
/// Deforming the lights.
/// </summary>
/// <param name="collision"></param>
/// <param name="impulse"></param>
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);
}
}
}
}
/// <summary>
/// Detaches the target wheel.
/// </summary>
/// <param name="wheelCollider"></param>
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<Rigidbody>();
GameObject clonedMeshCollider = new GameObject("Mesh Collider");
clonedMeshCollider.transform.SetParent(clonedWheel.transform, false);
clonedMeshCollider.transform.position = RCC_GetBounds.GetBoundsCenter(clonedWheel.transform);
MeshCollider mc = clonedMeshCollider.AddComponent<MeshCollider>();
MeshFilter biggestMesh = RCC_GetBounds.GetBiggestMesh(clonedWheel.transform);
mc.sharedMesh = biggestMesh.mesh;
mc.convex = true;
carController.ESPBroken = true;
}
/// <summary>
/// Raises the collision enter event.
/// </summary>
/// <param name="collision">Collision.</param>
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);
}
}
}
/// <summary>
/// Raises the collision enter event.
/// </summary>
/// <param name="collision">Collision.</param>
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;
}
/// <summary>
/// Finds closest vertex to the target point.
/// </summary>
/// <param name="trans"></param>
/// <param name="mf"></param>
/// <param name="point"></param>
/// <returns></returns>
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);
}
}