Advertisement
Guest User

Untitled

a guest
Sep 26th, 2024
43
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 16.96 KB | Software | 0 0
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEngine.Splines;
  4. using System.Linq;
  5.  
  6. #if UNITY_EDITOR
  7. using UnityEditor;
  8. #endif
  9.  
  10. /// <summary>
  11. /// A component for creating a flat-shaded extruded mesh from a Spline.
  12. /// </summary>
  13. [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
  14. [AddComponentMenu("Splines/Flat Shaded Spline Extrude")]
  15. [ExecuteAlways]
  16. public class FlatShadedSplineExtrude : MonoBehaviour
  17. {
  18.     [SerializeField, Tooltip("The Spline to extrude.")]
  19.     SplineContainer m_Container;
  20.  
  21.     [SerializeField, Tooltip("Enable to regenerate the extruded mesh when the target Spline is modified. Disable " +
  22.          "this option if the Spline will not be modified at runtime.")]
  23.     bool m_RebuildOnSplineChange = true;
  24.  
  25.     [SerializeField, Tooltip("The maximum number of times per-second that the mesh will be rebuilt.")]
  26.     int m_RebuildFrequency = 30;
  27.  
  28.     [SerializeField, Tooltip("Automatically update any Mesh, Box, or Sphere collider components when the mesh is extruded.")]
  29.     bool m_UpdateColliders = true;
  30.  
  31.     [SerializeField, Tooltip("The number of sides that comprise the radius of the mesh.")]
  32.     int m_Sides = 8;
  33.  
  34.     [SerializeField, Tooltip("The number of edge loops that comprise the length of one unit of the mesh. The " +
  35.          "total number of sections is equal to \"Spline.GetLength() * segmentsPerUnit\".")]
  36.     float m_SegmentsPerUnit = 4;
  37.  
  38.     [SerializeField, Tooltip("Indicates if the start and end of the mesh are filled. When the Spline is closed this setting is ignored.")]
  39.     bool m_Capped = true;
  40.  
  41.     [SerializeField, Tooltip("The radius of the extruded mesh.")]
  42.     float m_Radius = .25f;
  43.  
  44.     [SerializeField, Tooltip("The section of the Spline to extrude.")]
  45.     Vector2 m_Range = new Vector2(0f, 1f);
  46.  
  47.     [SerializeField, Tooltip("Rotation offset in degrees around the spline path axis.")]
  48.     float m_RotationOffsetDegrees = 0f;
  49.  
  50.     // Normals debugging gizmos
  51.     [SerializeField, Tooltip("Draw normals in the scene view.")]
  52.     bool m_DebugDrawNormals = false;
  53.  
  54.     Mesh m_Mesh;
  55.     bool m_RebuildRequested;
  56.     float m_NextScheduledRebuild;
  57.  
  58.     /// <summary>The SplineContainer of the <see cref="Spline"/> to extrude.</summary>
  59.     public SplineContainer Container
  60.     {
  61.         get => m_Container;
  62.         set => m_Container = value;
  63.     }
  64.  
  65.     /// <summary>
  66.     /// Enable to regenerate the extruded mesh when the target Spline is modified. Disable this option if the Spline
  67.     /// will not be modified at runtime.
  68.     /// </summary>
  69.     public bool RebuildOnSplineChange
  70.     {
  71.         get => m_RebuildOnSplineChange;
  72.         set => m_RebuildOnSplineChange = value;
  73.     }
  74.  
  75.     /// <summary>The maximum number of times per-second that the mesh will be rebuilt.</summary>
  76.     public int RebuildFrequency
  77.     {
  78.         get => m_RebuildFrequency;
  79.         set => m_RebuildFrequency = Mathf.Max(value, 1);
  80.     }
  81.  
  82.     /// <summary>How many sides make up the radius of the mesh.</summary>
  83.     public int Sides
  84.     {
  85.         get => m_Sides;
  86.         set => m_Sides = Mathf.Max(value, 3);
  87.     }
  88.  
  89.     /// <summary>How many edge loops comprise the one unit length of the mesh.</summary>
  90.     public float SegmentsPerUnit
  91.     {
  92.         get => m_SegmentsPerUnit;
  93.         set => m_SegmentsPerUnit = Mathf.Max(value, .0001f);
  94.     }
  95.  
  96.     /// <summary>Whether the start and end of the mesh is filled. This setting is ignored when spline is closed.</summary>
  97.     public bool Capped
  98.     {
  99.         get => m_Capped;
  100.         set => m_Capped = value;
  101.     }
  102.  
  103.     /// <summary>The radius of the extruded mesh.</summary>
  104.     public float Radius
  105.     {
  106.         get => m_Radius;
  107.         set => m_Radius = Mathf.Max(value, .00001f);
  108.     }
  109.  
  110.     /// <summary>
  111.     /// The section of the Spline to extrude.
  112.     /// </summary>
  113.     public Vector2 Range
  114.     {
  115.         get => m_Range;
  116.         set => m_Range = new Vector2(Mathf.Min(value.x, value.y), Mathf.Max(value.x, value.y));
  117.     }
  118.  
  119.     /// <summary>
  120.     /// Rotation offset in degrees around the spline path axis.
  121.     /// </summary>
  122.     public float RotationOffsetDegrees
  123.     {
  124.         get => m_RotationOffsetDegrees;
  125.         set => m_RotationOffsetDegrees = value;
  126.     }
  127.  
  128.     /// <summary>The main Spline to extrude.</summary>
  129.     public Spline Spline
  130.     {
  131.         get => m_Container?.Spline;
  132.     }
  133.  
  134.     /// <summary>The Splines to extrude.</summary>
  135.     public IReadOnlyList<Spline> Splines
  136.     {
  137.         get => m_Container?.Splines;
  138.     }
  139.  
  140.     void Reset()
  141.     {
  142.         TryGetComponent(out m_Container);
  143.  
  144.         if (TryGetComponent<MeshFilter>(out var filter))
  145.             filter.sharedMesh = m_Mesh = CreateMeshAsset();
  146.  
  147.         if (TryGetComponent<MeshRenderer>(out var renderer) && renderer.sharedMaterial == null)
  148.         {
  149.             // Create a default material
  150.             var defaultMaterial = new Material(Shader.Find("Standard"));
  151.             renderer.sharedMaterial = defaultMaterial;
  152.         }
  153.  
  154.         Rebuild();
  155.     }
  156.  
  157.     void Start()
  158.     {
  159. #if UNITY_EDITOR
  160.         if (EditorApplication.isPlaying)
  161. #endif
  162.         {
  163.             if (m_Container == null || m_Container.Spline == null || m_Container.Splines.Count == 0)
  164.                 return;
  165.             if ((m_Mesh = GetComponent<MeshFilter>().sharedMesh) == null)
  166.                 return;
  167.         }
  168.  
  169.         Rebuild();
  170.     }
  171.  
  172.     static readonly string k_EmptyContainerError = "Spline Extrude does not have a valid SplineContainer set.";
  173.     bool IsNullOrEmptyContainer()
  174.     {
  175.         var isNull = m_Container == null || m_Container.Spline == null || m_Container.Splines.Count == 0;
  176.         if (isNull)
  177.         {
  178.             if (Application.isPlaying)
  179.                 Debug.LogError(k_EmptyContainerError, this);
  180.         }
  181.         return isNull;
  182.     }
  183.  
  184.     static readonly string k_EmptyMeshFilterError = "SplineExtrude.createMeshInstance is disabled," +
  185.                                                            " but there is no valid mesh assigned. " +
  186.                                                            "Please create or assign a writable mesh asset.";
  187.     bool IsNullOrEmptyMeshFilter()
  188.     {
  189.         var isNull = (m_Mesh = GetComponent<MeshFilter>().sharedMesh) == null;
  190.         if (isNull)
  191.             Debug.LogError(k_EmptyMeshFilterError, this);
  192.         return isNull;
  193.     }
  194.  
  195.     void OnEnable()
  196.     {
  197.         if (Spline != null)
  198.             Spline.Changed += OnSplineChanged;
  199.     }
  200.  
  201.     void OnDisable()
  202.     {
  203.         if (Spline != null)
  204.             Spline.Changed -= OnSplineChanged;
  205.     }
  206.  
  207.     void OnSplineChanged(Spline spline, int knotIndex, SplineModification modificationType)
  208.     {
  209.         if (m_Container != null && Splines != null && Splines.Contains(spline) && m_RebuildOnSplineChange)
  210.         {
  211.             if (Application.isPlaying)
  212.             {
  213.                 m_RebuildRequested = true;
  214.             }
  215.             else
  216.             {
  217.                 Rebuild();
  218. #if UNITY_EDITOR
  219.                 // Force the scene view to repaint
  220.                 SceneView.RepaintAll();
  221. #endif
  222.             }
  223.         }
  224.     }
  225.  
  226.  
  227.     void Update()
  228.     {
  229.         if (m_RebuildRequested && Time.time >= m_NextScheduledRebuild)
  230.         {
  231.             Rebuild();
  232.             m_RebuildRequested = false;
  233.         }
  234.     }
  235.  
  236.     /// <summary>
  237.     /// Triggers the rebuild of a Spline's extrusion mesh and collider.
  238.     /// </summary>
  239.     public void Rebuild()
  240.     {
  241.         if (IsNullOrEmptyContainer() || IsNullOrEmptyMeshFilter())
  242.             return;
  243.  
  244.         ExtrudeMeshWithFlatShading();
  245.  
  246.         m_NextScheduledRebuild = Time.time + 1f / m_RebuildFrequency;
  247.  
  248. #if UNITY_PHYSICS_MODULE
  249.         if (m_UpdateColliders)
  250.         {
  251.             if (TryGetComponent<MeshCollider>(out var meshCollider))
  252.                 meshCollider.sharedMesh = m_Mesh;
  253.  
  254.             if (TryGetComponent<BoxCollider>(out var boxCollider))
  255.             {
  256.                 boxCollider.center = m_Mesh.bounds.center;
  257.                 boxCollider.size = m_Mesh.bounds.size;
  258.             }
  259.  
  260.             if (TryGetComponent<SphereCollider>(out var sphereCollider))
  261.             {
  262.                 sphereCollider.center = m_Mesh.bounds.center;
  263.                 var ext = m_Mesh.bounds.extents;
  264.                 sphereCollider.radius = Mathf.Max(ext.x, ext.y, ext.z);
  265.             }
  266.         }
  267. #endif
  268.     }
  269.  
  270. #if UNITY_EDITOR
  271.     void OnValidate()
  272.     {
  273.         if (EditorApplication.isPlaying)
  274.             return;
  275.         Rebuild();
  276.     }
  277. #endif
  278.  
  279.     Mesh CreateMeshAsset()
  280.     {
  281.         var mesh = new Mesh();
  282.         mesh.name = name;
  283.  
  284. #if UNITY_EDITOR
  285.         var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
  286.         var sceneDataDir = "Assets";
  287.  
  288.         if (!string.IsNullOrEmpty(scene.path))
  289.         {
  290.             var dir = System.IO.Path.GetDirectoryName(scene.path);
  291.             sceneDataDir = $"{dir}/{System.IO.Path.GetFileNameWithoutExtension(scene.path)}";
  292.             if (!System.IO.Directory.Exists(sceneDataDir))
  293.                 System.IO.Directory.CreateDirectory(sceneDataDir);
  294.         }
  295.  
  296.         var path = AssetDatabase.GenerateUniqueAssetPath($"{sceneDataDir}/SplineExtrude_{mesh.name}.asset");
  297.         AssetDatabase.CreateAsset(mesh, path);
  298.         EditorGUIUtility.PingObject(mesh);
  299. #endif
  300.         return mesh;
  301.     }
  302.  
  303.     // Custom method to extrude mesh with flat shading
  304.     void ExtrudeMeshWithFlatShading()
  305.     {
  306.         // Prepare variables
  307.         Spline spline = m_Container.Spline;
  308.  
  309.         if (spline == null)
  310.             return;
  311.  
  312.         List<Vector3> vertices = new List<Vector3>();
  313.         List<int> triangles = new List<int>();
  314.         List<Vector2> uvs = new List<Vector2>();
  315.         List<Vector3> normals = new List<Vector3>();
  316.  
  317.         // Calculate total length and number of segments
  318.         float splineLength = spline.GetLength();
  319.         int totalSegments = Mathf.Max(1, Mathf.CeilToInt(splineLength * m_SegmentsPerUnit));
  320.  
  321.         // Adjust range
  322.         float startRange = Mathf.Clamp01(m_Range.x);
  323.         float endRange = Mathf.Clamp01(m_Range.y);
  324.  
  325.         // Calculate rotation offset in radians
  326.         float rotationOffsetRadians = m_RotationOffsetDegrees * Mathf.Deg2Rad;
  327.  
  328.         // Generate vertices and triangles
  329.         for (int i = 0; i < totalSegments; i++)
  330.         {
  331.             float t0 = Mathf.Lerp(startRange, endRange, (float)i / totalSegments);
  332.             float t1 = Mathf.Lerp(startRange, endRange, (float)(i + 1) / totalSegments);
  333.  
  334.             Vector3 pos0 = spline.EvaluatePosition(t0);
  335.             Vector3 pos1 = spline.EvaluatePosition(t1);
  336.  
  337.             Vector3 tangent0 = spline.EvaluateTangent(t0);
  338.             Vector3 tangent1 = spline.EvaluateTangent(t1);
  339.  
  340.             Vector3 up0 = spline.EvaluateUpVector(t0);
  341.             Vector3 up1 = spline.EvaluateUpVector(t1);
  342.  
  343.             Quaternion rot0 = Quaternion.LookRotation(tangent0, up0);
  344.             Quaternion rot1 = Quaternion.LookRotation(tangent1, up1);
  345.  
  346.             for (int j = 0; j < m_Sides; j++)
  347.             {
  348.                 float angle0 = ((float)j / m_Sides) * Mathf.PI * 2f + rotationOffsetRadians;
  349.                 float angle1 = ((float)(j + 1) / m_Sides) * Mathf.PI * 2f + rotationOffsetRadians;
  350.  
  351.                 Vector3 offset0 = new Vector3(Mathf.Cos(angle0), Mathf.Sin(angle0), 0) * m_Radius;
  352.                 Vector3 offset1 = new Vector3(Mathf.Cos(angle1), Mathf.Sin(angle1), 0) * m_Radius;
  353.  
  354.                 // Positions of the four corners of the face
  355.                 Vector3 v0 = pos0 + rot0 * offset0;
  356.                 Vector3 v1 = pos1 + rot1 * offset0;
  357.                 Vector3 v2 = pos1 + rot1 * offset1;
  358.                 Vector3 v3 = pos0 + rot0 * offset1;
  359.  
  360.                 // Add vertices (duplicated for each face)
  361.                 int baseIndex = vertices.Count;
  362.                 vertices.Add(v0);
  363.                 vertices.Add(v1);
  364.                 vertices.Add(v2);
  365.                 vertices.Add(v3);
  366.  
  367.                 // Triangles (corrected winding order)
  368.                 triangles.Add(baseIndex + 2);
  369.                 triangles.Add(baseIndex + 1);
  370.                 triangles.Add(baseIndex);
  371.  
  372.                 triangles.Add(baseIndex + 3);
  373.                 triangles.Add(baseIndex + 2);
  374.                 triangles.Add(baseIndex);
  375.  
  376.                 // Normal per face
  377.                 Vector3 faceNormal = Vector3.Cross(v1 - v0, v2 - v0).normalized * -1;
  378.                 normals.Add(faceNormal);
  379.                 normals.Add(faceNormal);
  380.                 normals.Add(faceNormal);
  381.                 normals.Add(faceNormal);
  382.  
  383.                 // UVs
  384.                 uvs.Add(new Vector2(0, t0));
  385.                 uvs.Add(new Vector2(0, t1));
  386.                 uvs.Add(new Vector2(1, t1));
  387.                 uvs.Add(new Vector2(1, t0));
  388.             }
  389.         }
  390.  
  391.         // Generate end caps if required
  392.         if (m_Capped && !spline.Closed)
  393.         {
  394.             GenerateEndCaps(vertices, normals, uvs, triangles, rotationOffsetRadians);
  395.         }
  396.  
  397.         // Create the mesh
  398.         if (m_Mesh == null)
  399.             m_Mesh = CreateMeshAsset();
  400.  
  401.         m_Mesh.Clear();
  402.         m_Mesh.SetVertices(vertices);
  403.         m_Mesh.SetTriangles(triangles, 0);
  404.         m_Mesh.SetNormals(normals);
  405.         m_Mesh.SetUVs(0, uvs);
  406.         m_Mesh.RecalculateBounds();
  407.  
  408.         GetComponent<MeshFilter>().sharedMesh = m_Mesh;
  409.     }
  410.  
  411.     // Method to generate end caps with flat shading
  412.     void GenerateEndCaps(List<Vector3> vertices, List<Vector3> normals, List<Vector2> uvs, List<int> triangles, float rotationOffsetRadians)
  413.     {
  414.         Spline spline = m_Container.Spline;
  415.  
  416.         // Start Cap
  417.         {
  418.             float t = Mathf.Clamp01(m_Range.x);
  419.             Vector3 position = spline.EvaluatePosition(t);
  420.             Vector3 tangent = spline.EvaluateTangent(t);
  421.             Vector3 up = spline.EvaluateUpVector(t);
  422.  
  423.             Quaternion rotation = Quaternion.LookRotation(-tangent, up);
  424.  
  425.             int baseIndex = vertices.Count;
  426.  
  427.             // Center vertex
  428.             vertices.Add(position);
  429.             normals.Add(-tangent.normalized);
  430.             uvs.Add(new Vector2(0.5f, 0.5f));
  431.  
  432.             // Rim vertices
  433.             for (int i = 0; i < m_Sides; i++)
  434.             {
  435.                 float angle = ((float)i / m_Sides) * Mathf.PI * 2f - rotationOffsetRadians;
  436.                 Vector3 offset = new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * m_Radius;
  437.                 Vector3 v = position + rotation * offset;
  438.  
  439.                 vertices.Add(v);
  440.                 normals.Add(-tangent.normalized);
  441.                 uvs.Add(new Vector2(Mathf.Cos(angle) * 0.5f + 0.5f, Mathf.Sin(angle) * 0.5f + 0.5f));
  442.             }
  443.  
  444.             // Triangles
  445.             // Triangles
  446.             for (int i = 0; i < m_Sides; i++)
  447.             {
  448.                 int nextI = (i + 1) % m_Sides;
  449.                 triangles.Add(baseIndex);
  450.                 triangles.Add(baseIndex + i + 1);
  451.                 triangles.Add(baseIndex + nextI + 1);
  452.             }
  453.         }
  454.  
  455.         // End Cap
  456.         {
  457.             float t = Mathf.Clamp01(m_Range.y);
  458.             Vector3 position = spline.EvaluatePosition(t);
  459.             Vector3 tangent = spline.EvaluateTangent(t);
  460.             Vector3 up = spline.EvaluateUpVector(t);
  461.  
  462.             Quaternion rotation = Quaternion.LookRotation(tangent, up);
  463.  
  464.             int baseIndex = vertices.Count;
  465.  
  466.             // Center vertex
  467.             vertices.Add(position);
  468.             normals.Add(tangent.normalized);
  469.             uvs.Add(new Vector2(0.5f, 0.5f));
  470.  
  471.             // Rim vertices
  472.             for (int i = 0; i < m_Sides; i++)
  473.             {
  474.                 float angle = ((float)i / m_Sides) * Mathf.PI * 2f + rotationOffsetRadians;
  475.                 Vector3 offset = new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * m_Radius;
  476.                 Vector3 v = position + rotation * offset;
  477.  
  478.                 vertices.Add(v);
  479.                 normals.Add(tangent.normalized);
  480.                 uvs.Add(new Vector2(Mathf.Cos(angle) * 0.5f + 0.5f, Mathf.Sin(angle) * 0.5f + 0.5f));
  481.             }
  482.  
  483.             // Triangles (corrected winding order)
  484.             for (int i = 0; i < m_Sides; i++)
  485.             {
  486.                 int nextI = (i + 1) % m_Sides;
  487.                 triangles.Add(baseIndex);
  488.                 triangles.Add(baseIndex + i + 1);
  489.                 triangles.Add(baseIndex + nextI + 1);
  490.             }
  491.         }
  492.     }
  493.  
  494.     void OnDrawGizmos()
  495.     {
  496.         if (m_Mesh == null) return;
  497.  
  498.         if(m_DebugDrawNormals == false) return;
  499.  
  500.         // Draw normals in the scene view
  501.         for (int i = 0; i < m_Mesh.vertexCount; i++)
  502.         {
  503.             Gizmos.color = Color.yellow;
  504.  
  505.             // Get the vertex and normal from the mesh
  506.             Vector3 vertex = transform.TransformPoint(m_Mesh.vertices[i]); // Transform to world space
  507.             Vector3 normal = m_Mesh.normals[i];
  508.  
  509.             // Draw the normal starting from the vertex
  510.             Gizmos.DrawLine(vertex, vertex + normal * 0.2f);  // Scale for visibility
  511.         }
  512.     }
  513. }
  514.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement