using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; #if UNITY_EDITOR using UnityEditor.Experimental.SceneManagement; #endif namespace SplineMesh { /// /// Deform a mesh and place it along a spline, given various parameters. /// /// This class intend to cover the most common situations of mesh bending. It can be used as-is in your project, /// or can serve as a source of inspiration to write your own procedural generator. /// [ExecuteInEditMode] [SelectionBase] [DisallowMultipleComponent] public class SplineMeshTiling : MonoBehaviour { private GameObject generated; private Spline spline = null; private bool toUpdate = false; [Tooltip("Mesh to bend along the spline.")] public Mesh mesh; [Tooltip("Material to apply on the bent mesh.")] public Material material; [Tooltip("Physic material to apply on the bent mesh.")] public PhysicMaterial physicMaterial; [Tooltip("Translation to apply on the mesh before bending it.")] public Vector3 translation; [Tooltip("Rotation to apply on the mesh before bending it.")] public Vector3 rotation; [Tooltip("Scale to apply on the mesh before bending it.")] public Vector3 scale = Vector3.one; [Tooltip("If true, a mesh collider will be generated.")] public bool generateCollider = true; [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.")] public bool updateInPlayMode; [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.")] public bool curveSpace = false; [Tooltip("The mode to use to fill the choosen interval with the bent mesh.")] public MeshBender.FillingMode mode = MeshBender.FillingMode.StretchToInterval; private void OnEnable() { // tip : if you name all generated content in the same way, you can easily find all of it // at once in the scene view, with a single search. string generatedName = "generated by " + GetType().Name; var generatedTranform = transform.Find(generatedName); generated = generatedTranform != null ? generatedTranform.gameObject : UOUtility.Create(generatedName, gameObject); spline = GetComponentInParent(); spline.NodeListChanged += (s, e) => toUpdate = true; toUpdate = true; } private void OnValidate() { if (spline == null) return; toUpdate = true; } private void Update() { // we can prevent the generated content to be updated during playmode to preserve baked data saved in the scene if (!updateInPlayMode && Application.isPlaying) return; if (toUpdate) { toUpdate = false; CreateMeshes(); } } public void CreateMeshes() { #if UNITY_EDITOR // we don't update if we are in prefab mode if (PrefabStageUtility.GetCurrentPrefabStage() != null) return; #endif var used = new List(); if (curveSpace) { int i = 0; foreach (var curve in spline.curves) { var go = FindOrCreate("segment " + i++ + " mesh"); go.GetComponent().SetInterval(curve); go.GetComponent().enabled = generateCollider; used.Add(go); } } else { var go = FindOrCreate("segment 1 mesh"); go.GetComponent().SetInterval(spline, 0); go.GetComponent().enabled = generateCollider; used.Add(go); } // we destroy the unused objects. This is classic pooling to recycle game objects. foreach (var go in generated.transform .Cast() .Select(child => child.gameObject).Except(used)) { UOUtility.Destroy(go); } } private GameObject FindOrCreate(string name) { var childTransform = generated.transform.Find(name); GameObject res; if (childTransform == null) { res = UOUtility.Create(name, generated, typeof(MeshFilter), typeof(MeshRenderer), typeof(MeshBender), typeof(MeshCollider)); res.isStatic = !updateInPlayMode; } else { res = childTransform.gameObject; } res.GetComponent().material = material; res.GetComponent().material = physicMaterial; MeshBender mb = res.GetComponent(); mb.Source = SourceMesh.Build(mesh) .Translate(translation) .Rotate(Quaternion.Euler(rotation)) .Scale(scale); mb.Mode = mode; return res; } } }