H_SafeExperienceDrivingSystem/U3D_DrivingSystem/Assets/EVP5/Scripts/Ground Materials/TireMarksRenderer.cs

348 lines
9.2 KiB
C#

//------------------------------------------------------------------------------------------------
// Edy's Vehicle Physics
// (c) Angel Garcia "Edy" - Oviedo, Spain
// http://www.edy.es
//------------------------------------------------------------------------------------------------
//
// Optimization notes:
//
// Tire marks is a dynamically created mesh that gets updated whenever new marks are added.
//
// - maxMarks: less is faster. Vertices, normals etc. are passed to the renderer on each change.
// - fadeOutRange: smaller is faster. Each fading segment is adjusted on each change.
using UnityEngine;
using UnityEngine.Rendering;
namespace EVP
{
// Variables for each mark point created.
// A mark segment will be generated from each two consecutive marks.
public class MarkPoint
{
public Vector3 pos = Vector3.zero;
public Vector3 normal = Vector3.zero;
public Vector4 tangent = Vector4.zero;
public Vector3 posl = Vector3.zero;
public Vector3 posr = Vector3.zero;
public float intensity = 0.0f;
public int lastIndex = 0;
}
public class TireMarksRenderer : MonoBehaviour
{
public enum Mode { PressureAndSkid, PressureOnly }
public Mode mode = Mode.PressureAndSkid;
[Range(0,1)]
public float pressureBoost = 0.0f;
[Space(5)]
public int maxMarks = 1024; // Maximum number of mark segments that can be rendered
public float minDistance = 0.1f; // Minimum distance between two consecutive marks.
public float groundOffset = 0.02f; // Distance the marks are placed above the surface they're placed upon (m)
public float textureOffsetY = 0.05f; // UV tweak for texture continuity
[Range(0,1)]
public float fadeOutRange = 0.5f; // Proportion of the marks that are fade out
public Material material; // Material used for the marks
int m_markCount;
int m_markArraySize = 0;
MarkPoint[] m_markPoints;
CommonTools.BiasLerpContext m_biasCtx = new CommonTools.BiasLerpContext();
bool m_segmentsUpdated;
int m_segmentCount;
int m_segmentArraySize;
Mesh m_mesh;
Vector3[] m_vertices;
Vector3[] m_normals;
Vector4[] m_tangents;
Color[] m_colors;
Vector2[] m_uvs;
int[] m_triangles;
Vector2[] m_values;
void OnEnable ()
{
// Tire marks manager must be located at the origin with no rotation
transform.position = Vector3.zero;
transform.rotation = Quaternion.identity;
// Create the mesh components for drawing the marks
MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>();
if (meshFilter == null)
meshFilter = gameObject.AddComponent<MeshFilter>();
MeshRenderer meshRenderer = gameObject.GetComponent<MeshRenderer>();
if (meshRenderer == null)
{
meshRenderer = gameObject.AddComponent<MeshRenderer>();
meshRenderer.shadowCastingMode = ShadowCastingMode.Off;
meshRenderer.material = material;
}
if (maxMarks < 10) maxMarks = 10;
// Initialize mark points array
m_markPoints = new MarkPoint[maxMarks*2];
for (int i = 0, c = m_markPoints.Length; i < c; i++)
m_markPoints[i] = new MarkPoint();
m_markCount = 0;
m_markArraySize = m_markPoints.Length;
// Initialize the mesh source arrays.
// These will be edited and passed to the mesh filter in runtime.
m_vertices = new Vector3[maxMarks * 4];
m_normals = new Vector3[maxMarks * 4];
m_tangents = new Vector4[maxMarks * 4];
m_colors = new Color[maxMarks * 4];
m_uvs = new Vector2[maxMarks * 4];
m_triangles = new int[maxMarks * 6];
m_values = new Vector2[maxMarks];
m_segmentCount = 0;
m_segmentArraySize = maxMarks;
m_segmentsUpdated = false;
// Elements that will be invariant
for (int i = 0; i < m_segmentArraySize; i++)
{
m_uvs[i * 4 + 0] = new Vector2(0, textureOffsetY);
m_uvs[i * 4 + 1] = new Vector2(1, textureOffsetY);
m_uvs[i * 4 + 2] = new Vector2(0, 1-textureOffsetY);
m_uvs[i * 4 + 3] = new Vector2(1, 1-textureOffsetY);
m_triangles[i * 6 + 0] = i * 4 + 0;
m_triangles[i * 6 + 2] = i * 4 + 1;
m_triangles[i * 6 + 1] = i * 4 + 2;
m_triangles[i * 6 + 3] = i * 4 + 2;
m_triangles[i * 6 + 5] = i * 4 + 1;
m_triangles[i * 6 + 4] = i * 4 + 3;
}
// Initialize the mesh
m_mesh = new Mesh();
m_mesh.MarkDynamic();
m_mesh.vertices = m_vertices;
m_mesh.normals = m_normals;
m_mesh.tangents = m_tangents;
m_mesh.colors = m_colors;
m_mesh.triangles = m_triangles;
m_mesh.uv = m_uvs;
m_mesh.RecalculateBounds();
meshFilter.mesh = m_mesh;
}
void OnValidate ()
{
if (m_uvs != null)
{
for (int i = 0; i < m_uvs.Length/4; i++)
{
m_uvs[i * 4 + 0] = new Vector2(0, textureOffsetY);
m_uvs[i * 4 + 1] = new Vector2(1, textureOffsetY);
m_uvs[i * 4 + 2] = new Vector2(0, 1-textureOffsetY);
m_uvs[i * 4 + 3] = new Vector2(1, 1-textureOffsetY);
}
}
m_segmentsUpdated = true;
}
// Called externally for each point where a mark might be created.
//
// The lastIndex value is the last value returned by this method for the same wheel,
// and allows creating different continuous mark treads for each wheel.
// lastIndex = -1 starts a new segment.
public int AddMark (Vector3 pos, Vector3 normal, float pressureRatio, float skidRatio, float width, int lastIndex)
{
if (!isActiveAndEnabled || m_markArraySize == 0)
return -1;
pressureRatio = CommonTools.BiasedLerp(pressureRatio, 0.5f + pressureBoost*0.5f, m_biasCtx);
float intensity = 0.0f;
switch (mode)
{
case Mode.PressureAndSkid:
intensity = Mathf.Clamp01(pressureRatio * skidRatio);
break;
case Mode.PressureOnly:
intensity = Mathf.Clamp01(pressureRatio);
break;
}
if (intensity <= 0.0f) return -1;
if (intensity > 1.0f) intensity = 1.0f;
Vector3 newPos = pos + normal * groundOffset;
if (lastIndex >= 0 && Vector3.Distance(newPos, m_markPoints[lastIndex % m_markArraySize].pos) < minDistance)
return lastIndex;
// Create new mark segment
MarkPoint current = m_markPoints[m_markCount % m_markArraySize];
current.pos = newPos;
current.normal = normal;
current.intensity = intensity;
current.lastIndex = lastIndex;
if (lastIndex >= 0 && lastIndex > m_markCount - m_markArraySize)
{
MarkPoint last = m_markPoints[lastIndex % m_markArraySize];
Vector3 dir = (current.pos - last.pos);
Vector3 crossDir = Vector3.Cross(dir, normal).normalized;
Vector3 widthDir = 0.5f * width * crossDir;
current.posl = current.pos + widthDir;
current.posr = current.pos - widthDir;
current.tangent = new Vector4(crossDir.x, crossDir.y, crossDir.z, 1);
if (last.lastIndex < 0)
{
last.tangent = current.tangent;
last.posl = current.pos + widthDir;
last.posr = current.pos - widthDir;
}
// Add the actual segment to the mesh
AddSegment(last, current);
}
m_markCount++;
return m_markCount-1;
}
public void Clear ()
{
if (isActiveAndEnabled)
{
m_markCount = 0;
m_segmentCount = 0;
for (int i = 0, c = m_vertices.Length; i < c; i++)
m_vertices[i] = Vector3.zero;
m_mesh.vertices = m_vertices;
m_segmentsUpdated = true;
}
}
void AddSegment (MarkPoint first, MarkPoint second)
{
int segmentIndex = (m_segmentCount % m_segmentArraySize) * 4;
m_vertices[segmentIndex + 0] = first.posl;
m_vertices[segmentIndex + 1] = first.posr;
m_vertices[segmentIndex + 2] = second.posl;
m_vertices[segmentIndex + 3] = second.posr;
m_normals[segmentIndex + 0] = first.normal;
m_normals[segmentIndex + 1] = first.normal;
m_normals[segmentIndex + 2] = second.normal;
m_normals[segmentIndex + 3] = second.normal;
m_tangents[segmentIndex + 0] = first.tangent;
m_tangents[segmentIndex + 1] = first.tangent;
m_tangents[segmentIndex + 2] = second.tangent;
m_tangents[segmentIndex + 3] = second.tangent;
m_colors[segmentIndex + 0].a = first.intensity;
m_colors[segmentIndex + 1].a = first.intensity;
m_colors[segmentIndex + 2].a = second.intensity;
m_colors[segmentIndex + 3].a = second.intensity;
m_values[segmentIndex/4] = new Vector2(first.intensity, second.intensity);
// The very first segment moves all vertices to that position.
// This allows proper calculation of the bounds later.
if (m_segmentCount == 0)
{
Vector3 v = m_vertices[0];
for (int i = 4, c = m_vertices.Length; i < c; i++)
m_vertices[i] = v;
}
m_segmentCount++;
m_segmentsUpdated = true;
}
void LateUpdate ()
{
if (!m_segmentsUpdated) return;
m_segmentsUpdated = false;
// Progressively fade out the older marks
int toFade = (int)(m_segmentArraySize * fadeOutRange);
if (toFade > 0)
{
int segment = m_segmentCount - m_segmentArraySize;
int fadeStart = 0;
if (segment < 0)
{
fadeStart = -segment;
segment = 0;
}
float fadeStep = 1.0f / toFade;
for (int i = fadeStart; i < toFade; i++)
{
int valueIndex = segment % m_segmentArraySize;
int colorIndex = valueIndex * 4;
float decay = i * fadeStep;
float intensity1 = m_values[valueIndex].x * decay;
float intensity2 = m_values[valueIndex].y * decay + fadeStep;
m_colors[colorIndex + 0].a = intensity1;
m_colors[colorIndex + 1].a = intensity1;
m_colors[colorIndex + 2].a = intensity2;
m_colors[colorIndex + 3].a = intensity2;
segment++;
}
}
// Update the mesh
m_mesh.MarkDynamic();
m_mesh.vertices = m_vertices;
m_mesh.normals = m_normals;
m_mesh.tangents = m_tangents;
m_mesh.colors = m_colors;
m_mesh.RecalculateBounds();
}
}
}