SplineMeshTiling.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using UnityEngine;
  6. #if UNITY_EDITOR
  7. using UnityEditor.Experimental.SceneManagement;
  8. #endif
  9. namespace SplineMesh {
  10. /// <summary>
  11. /// Deform a mesh and place it along a spline, given various parameters.
  12. ///
  13. /// This class intend to cover the most common situations of mesh bending. It can be used as-is in your project,
  14. /// or can serve as a source of inspiration to write your own procedural generator.
  15. /// </summary>
  16. [ExecuteInEditMode]
  17. [SelectionBase]
  18. [DisallowMultipleComponent]
  19. public class SplineMeshTiling : MonoBehaviour {
  20. private GameObject generated;
  21. private Spline spline = null;
  22. private bool toUpdate = false;
  23. [Tooltip("Mesh to bend along the spline.")]
  24. public Mesh mesh;
  25. [Tooltip("Material to apply on the bent mesh.")]
  26. public Material material;
  27. [Tooltip("Physic material to apply on the bent mesh.")]
  28. public PhysicMaterial physicMaterial;
  29. [Tooltip("Translation to apply on the mesh before bending it.")]
  30. public Vector3 translation;
  31. [Tooltip("Rotation to apply on the mesh before bending it.")]
  32. public Vector3 rotation;
  33. [Tooltip("Scale to apply on the mesh before bending it.")]
  34. public Vector3 scale = Vector3.one;
  35. [Tooltip("If true, a mesh collider will be generated.")]
  36. public bool generateCollider = true;
  37. [Tooltip("If true, the mesh will be bent on play mode. If false, the bent mesh will be kept from the editor mode, allowing lighting baking.")]
  38. public bool updateInPlayMode;
  39. [Tooltip("If true, a mesh will be placed on each curve of the spline. If false, a single mesh will be placed for the whole spline.")]
  40. public bool curveSpace = false;
  41. [Tooltip("The mode to use to fill the choosen interval with the bent mesh.")]
  42. public MeshBender.FillingMode mode = MeshBender.FillingMode.StretchToInterval;
  43. private void OnEnable() {
  44. // tip : if you name all generated content in the same way, you can easily find all of it
  45. // at once in the scene view, with a single search.
  46. string generatedName = "generated by " + GetType().Name;
  47. var generatedTranform = transform.Find(generatedName);
  48. generated = generatedTranform != null ? generatedTranform.gameObject : UOUtility.Create(generatedName, gameObject);
  49. spline = GetComponentInParent<Spline>();
  50. spline.NodeListChanged += (s, e) => toUpdate = true;
  51. toUpdate = true;
  52. }
  53. private void OnValidate() {
  54. if (spline == null) return;
  55. toUpdate = true;
  56. }
  57. private void Update() {
  58. // we can prevent the generated content to be updated during playmode to preserve baked data saved in the scene
  59. if (!updateInPlayMode && Application.isPlaying) return;
  60. if (toUpdate) {
  61. toUpdate = false;
  62. CreateMeshes();
  63. }
  64. }
  65. public void CreateMeshes() {
  66. #if UNITY_EDITOR
  67. // we don't update if we are in prefab mode
  68. if (PrefabStageUtility.GetCurrentPrefabStage() != null) return;
  69. #endif
  70. var used = new List<GameObject>();
  71. if (curveSpace) {
  72. int i = 0;
  73. foreach (var curve in spline.curves) {
  74. var go = FindOrCreate("segment " + i++ + " mesh");
  75. go.GetComponent<MeshBender>().SetInterval(curve);
  76. go.GetComponent<MeshCollider>().enabled = generateCollider;
  77. used.Add(go);
  78. }
  79. } else {
  80. var go = FindOrCreate("segment 1 mesh");
  81. go.GetComponent<MeshBender>().SetInterval(spline, 0);
  82. go.GetComponent<MeshCollider>().enabled = generateCollider;
  83. used.Add(go);
  84. }
  85. // we destroy the unused objects. This is classic pooling to recycle game objects.
  86. foreach (var go in generated.transform
  87. .Cast<Transform>()
  88. .Select(child => child.gameObject).Except(used)) {
  89. UOUtility.Destroy(go);
  90. }
  91. }
  92. private GameObject FindOrCreate(string name) {
  93. var childTransform = generated.transform.Find(name);
  94. GameObject res;
  95. if (childTransform == null) {
  96. res = UOUtility.Create(name,
  97. generated,
  98. typeof(MeshFilter),
  99. typeof(MeshRenderer),
  100. typeof(MeshBender),
  101. typeof(MeshCollider));
  102. res.isStatic = !updateInPlayMode;
  103. } else {
  104. res = childTransform.gameObject;
  105. }
  106. res.GetComponent<MeshRenderer>().material = material;
  107. res.GetComponent<MeshCollider>().material = physicMaterial;
  108. MeshBender mb = res.GetComponent<MeshBender>();
  109. mb.Source = SourceMesh.Build(mesh)
  110. .Translate(translation)
  111. .Rotate(Quaternion.Euler(rotation))
  112. .Scale(scale);
  113. mb.Mode = mode;
  114. return res;
  115. }
  116. }
  117. }