using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif using System.Collections; using System.Collections.Generic; using System.Threading; namespace Dreamteck.Splines { [AddComponentMenu("Dreamteck/Splines/Users/Object Bender")] public class ObjectBender : SplineUser { public enum Axis { X, Y, Z } public enum NormalMode { Spline, Auto, Custom } public enum ForwardMode { Spline, Custom } public bool bend { get { return _bend; } set { if(_bend != value) { _bend = value; if (value) { UpdateReferences(); Rebuild(); } else Revert(); } } } [SerializeField] [HideInInspector] private bool _bend = false; public Axis axis { get { return _axis; } set { if (spline != null && value != _axis) { _axis = value; UpdateReferences(); Rebuild(); } else _axis = value; } } public NormalMode upMode { get { return _normalMode; } set { if (spline != null && value != _normalMode) { _normalMode = value; Rebuild(); } else _normalMode = value; } } public Vector3 customNormal { get { return _customNormal; } set { if (spline != null && value != _customNormal) { _customNormal = value; Rebuild(); } else _customNormal = value; } } public ForwardMode forwardMode { get { return _forwardMode; } set { if (spline != null && value != _forwardMode) { _forwardMode = value; Rebuild(); } else _forwardMode = value; } } public Vector3 customForward { get { return _customForward; } set { if (spline != null && value != _customForward) { _customForward = value; Rebuild(); } else _customForward = value; } } [HideInInspector] public BendProperty[] bendProperties = new BendProperty[0]; [SerializeField] [HideInInspector] private bool _parentIsTheSpline = false; [SerializeField] [HideInInspector] private TS_Bounds bounds = null; [SerializeField] [HideInInspector] private Axis _axis = Axis.Z; [SerializeField] [HideInInspector] private NormalMode _normalMode = NormalMode.Auto; [SerializeField] [HideInInspector] private ForwardMode _forwardMode = ForwardMode.Spline; [SerializeField] [HideInInspector] [UnityEngine.Serialization.FormerlySerializedAs("_upVector")] private Vector3 _customNormal = Vector3.up; [SerializeField] [HideInInspector] private Vector3 _customForward = Vector3.forward; Matrix4x4 normalMatrix = new Matrix4x4(); Quaternion bendRotation = Quaternion.identity; private void GetTransformsRecursively(Transform current, ref List transformList) { transformList.Add(current); foreach (Transform child in current) { GetTransformsRecursively(child, ref transformList); } } private void GetObjects() { List found = new List(); GetTransformsRecursively(transform, ref found); BendProperty[] newProperties = new BendProperty[found.Count]; for (int i = 0; i < found.Count; i++) { CreateProperty(ref newProperties[i], found[i]); } bendProperties = newProperties; SplineComputer splineComponent = GetComponent(); _parentIsTheSpline = splineComponent == spline; } public TS_Bounds GetBounds() { return new TS_Bounds(bounds.min, bounds.max, bounds.center); } #if UNITY_EDITOR public void EditorGenerateLightmapUVs() { for (int i = 0; i < bendProperties.Length; i++) { if (bendProperties[i].bendMesh) { if (bendProperties[i].filter == null) continue; if (bendProperties[i].filter.sharedMesh == null) continue; EditorUtility.DisplayProgressBar("Generating Lightmap UVS", bendProperties[i].filter.sharedMesh.name, (float)i / (bendProperties.Length - 1)); Unwrapping.GenerateSecondaryUVSet(bendProperties[i].filter.sharedMesh); } } EditorUtility.ClearProgressBar(); } #endif private void CreateProperty(ref BendProperty property, Transform t) { property = new BendProperty(t, t == transform); //Create a new bend property for each child for (int i = 0; i < bendProperties.Length; i++) { //Search for properties that have the same trasform and copy their settings if (bendProperties[i].transform.transform == t) { property.enabled = bendProperties[i].enabled; property.applyRotation = bendProperties[i].applyRotation; property.applyScale = bendProperties[i].applyScale; property.bendMesh = bendProperties[i].bendMesh; property.bendCollider = bendProperties[i].bendCollider; property.generateLightmapUVs = bendProperties[i].generateLightmapUVs; property.colliderUpdateRate = bendProperties[i].colliderUpdateRate; break; } } if (t.transform != trs) { property.originalPosition = trs.InverseTransformPoint(t.position); property.originalRotation = Quaternion.Inverse(trs.rotation) * t.rotation; } } private void CalculateBounds() { if (bounds == null) bounds = new TS_Bounds(Vector3.zero, Vector3.zero); bounds.min = bounds.max = Vector3.zero; for (int i = 0; i < bendProperties.Length; i++) { CalculatePropertyBounds(ref bendProperties[i]); } for (int i = 0; i < bendProperties.Length; i++) { CalculatePercents(bendProperties[i]); } } private void CalculatePropertyBounds(ref BendProperty property) { if (!property.enabled) return; if (property.isParent && _parentIsTheSpline) return; if (property.transform.transform == trs) { if (0f < bounds.min.x) bounds.min.x = 0f; if (0f < bounds.min.y) bounds.min.y = 0f; if (0f < bounds.min.z) bounds.min.z = 0f; if (0f > bounds.max.x) bounds.max.x = 0f; if (0f > bounds.max.y) bounds.max.y = 0f; if (0f > bounds.max.z) bounds.max.z = 0f; } else { if (property.originalPosition.x < bounds.min.x) bounds.min.x = property.originalPosition.x; if (property.originalPosition.y < bounds.min.y) bounds.min.y = property.originalPosition.y; if (property.originalPosition.z < bounds.min.z) bounds.min.z = property.originalPosition.z; if (property.originalPosition.x > bounds.max.x) bounds.max.x = property.originalPosition.x; if (property.originalPosition.y > bounds.max.y) bounds.max.y = property.originalPosition.y; if (property.originalPosition.z > bounds.max.z) bounds.max.z = property.originalPosition.z; } if (property.editMesh != null) { for (int n = 0; n < property.editMesh.vertices.Length; n++) { Vector3 localPos = property.transform.TransformPoint(property.editMesh.vertices[n]); localPos = trs.InverseTransformPoint(localPos); if (localPos.x < bounds.min.x) bounds.min.x = localPos.x; if (localPos.y < bounds.min.y) bounds.min.y = localPos.y; if (localPos.z < bounds.min.z) bounds.min.z = localPos.z; if (localPos.x > bounds.max.x) bounds.max.x = localPos.x; if (localPos.y > bounds.max.y) bounds.max.y = localPos.y; if (localPos.z > bounds.max.z) bounds.max.z = localPos.z; } } if (property.editColliderMesh != null) { for (int n = 0; n < property.editColliderMesh.vertices.Length; n++) { Vector3 localPos = property.transform.TransformPoint(property.editColliderMesh.vertices[n]); localPos = trs.InverseTransformPoint(localPos); if (localPos.x < bounds.min.x) bounds.min.x = localPos.x; if (localPos.y < bounds.min.y) bounds.min.y = localPos.y; if (localPos.z < bounds.min.z) bounds.min.z = localPos.z; if (localPos.x > bounds.max.x) bounds.max.x = localPos.x; if (localPos.y > bounds.max.y) bounds.max.y = localPos.y; if (localPos.z > bounds.max.z) bounds.max.z = localPos.z; } } if (property.originalSpline != null) { for (int n = 0; n < property.originalSpline.points.Length; n++) { Vector3 localPos = trs.InverseTransformPoint(property.originalSpline.points[n].position); if (localPos.x < bounds.min.x) bounds.min.x = localPos.x; if (localPos.y < bounds.min.y) bounds.min.y = localPos.y; if (localPos.z < bounds.min.z) bounds.min.z = localPos.z; if (localPos.x > bounds.max.x) bounds.max.x = localPos.x; if (localPos.y > bounds.max.y) bounds.max.y = localPos.y; if (localPos.z > bounds.max.z) bounds.max.z = localPos.z; } } bounds.CreateFromMinMax(bounds.min, bounds.max); } public void CalculatePercents(BendProperty property) { if (property.transform.transform != trs) property.positionPercent = GetPercentage(trs.InverseTransformPoint(property.transform.position)); else property.positionPercent = GetPercentage(Vector3.zero); if (property.editMesh != null) { if (property.vertexPercents.Length != property.editMesh.vertexCount) property.vertexPercents = new Vector3[property.editMesh.vertexCount]; if (property.editColliderMesh != null) { if (property.colliderVertexPercents.Length != property.editMesh.vertexCount) property.colliderVertexPercents = new Vector3[property.editColliderMesh.vertexCount]; } for (int i = 0; i < property.editMesh.vertexCount; i++) { Vector3 localVertex = property.transform.TransformPoint(property.editMesh.vertices[i]); localVertex = trs.InverseTransformPoint(localVertex); property.vertexPercents[i] = GetPercentage(localVertex); } if (property.editColliderMesh != null) { for (int i = 0; i < property.editColliderMesh.vertexCount; i++) { Vector3 localVertex = property.transform.TransformPoint(property.editColliderMesh.vertices[i]); localVertex = trs.InverseTransformPoint(localVertex); property.colliderVertexPercents[i] = GetPercentage(localVertex); } } } if (property.splineComputer != null) { SplinePoint[] points = property.splineComputer.GetPoints(); property.splinePointPercents = new Vector3[points.Length]; property.primaryTangentPercents = new Vector3[points.Length]; property.secondaryTangentPercents = new Vector3[points.Length]; for (int i = 0; i < points.Length; i++) { property.splinePointPercents[i] = GetPercentage(trs.InverseTransformPoint(points[i].position)); property.primaryTangentPercents[i] = GetPercentage(trs.InverseTransformPoint(points[i].tangent)); property.secondaryTangentPercents[i] = GetPercentage(trs.InverseTransformPoint(points[i].tangent2)); } } } private void Revert() { for (int i = 0; i < bendProperties.Length; i++) { bendProperties[i].Revert(); } } public void UpdateReferences() { if (!hasTransform) { CacheTransform(); } if (_bend) { for (int i = 0; i < bendProperties.Length; i++) bendProperties[i].Revert(); } GetObjects(); CalculateBounds(); if (_bend) { Bend(); for (int i = 0; i < bendProperties.Length; i++) { bendProperties[i].Apply(i > 0 || trs != spline.transform); bendProperties[i].Update(); } } } private void GetevalResult(Vector3 percentage) { switch (axis) { case Axis.X: Evaluate(percentage.x, ref evalResult); break; case Axis.Y: Evaluate(percentage.y, ref evalResult); break; case Axis.Z: Evaluate(percentage.z, ref evalResult); break; } switch (_normalMode) { case NormalMode.Auto: evalResult.up = Vector3.Cross(evalResult.forward, evalResult.right); break; case NormalMode.Custom: evalResult.up = _customNormal; break; } if (_forwardMode == ForwardMode.Custom) evalResult.forward = customForward; ModifySample(ref evalResult); Vector3 right = evalResult.right; Quaternion axisRotation = Quaternion.identity; switch (axis) { case Axis.Z: evalResult.position += right * Mathf.Lerp(bounds.min.x, bounds.max.x, percentage.x) * evalResult.size; evalResult.position += evalResult.up * Mathf.Lerp(bounds.min.y, bounds.max.y, percentage.y) * evalResult.size; break; case Axis.X: axisRotation = Quaternion.Euler(0f, -90f, 0f); evalResult.position += right * Mathf.Lerp(bounds.max.z, bounds.min.z, percentage.z) * evalResult.size; evalResult.position += evalResult.up * Mathf.Lerp(bounds.min.y, bounds.max.y, percentage.y) * evalResult.size; break; case Axis.Y: axisRotation = Quaternion.Euler(90f, 0f, 0f); evalResult.position += right * Mathf.Lerp(bounds.min.x, bounds.max.x, percentage.x) * evalResult.size; evalResult.position += evalResult.up * Mathf.Lerp(bounds.min.z, bounds.max.z, percentage.z) * evalResult.size; break; } bendRotation = evalResult.rotation * axisRotation; normalMatrix = Matrix4x4.TRS(evalResult.position, bendRotation, Vector3.one * evalResult.size).inverse.transpose; } private Vector3 GetPercentage(Vector3 point) { point.x = Mathf.InverseLerp(bounds.min.x, bounds.max.x, point.x); point.y = Mathf.InverseLerp(bounds.min.y, bounds.max.y, point.y); point.z = Mathf.InverseLerp(bounds.min.z, bounds.max.z, point.z); return point; } protected override void Build() { base.Build(); if (_bend) Bend(); } private void Bend() { if (sampleCount <= 1) return; if (bendProperties.Length == 0) return; for (int i = 0; i < bendProperties.Length; i++) { BendObject(bendProperties[i]); } } public void BendObject(BendProperty p) { if (!p.enabled) return; if (p.isParent && _parentIsTheSpline) return; GetevalResult(p.positionPercent); p.transform.position = evalResult.position; if (p.applyRotation) { //p.transform.rotation = evalResult.rotation * axisRotation * p.originalRotation; p.transform.rotation = bendRotation * (Quaternion.Inverse(p.parentRotation) * p.originalRotation); } else p.transform.rotation = p.originalRotation; if (p.applyScale) p.transform.scale = p.originalScale * evalResult.size; Matrix4x4 toLocalMatrix = Matrix4x4.TRS(p.transform.position, p.transform.rotation, p.transform.scale).inverse; if (p.editMesh != null) { BendMesh(p.vertexPercents, p.normals, p.editMesh, toLocalMatrix); p.editMesh.hasUpdate = true; } if (p._editColliderMesh != null) { BendMesh(p.colliderVertexPercents, p.colliderNormals, p.editColliderMesh, toLocalMatrix); p.editColliderMesh.hasUpdate = true; } if (p.originalSpline != null && !p.isParent) { for (int n = 0; n < p.splinePointPercents.Length; n++) { SplinePoint point = p.originalSpline.points[n]; GetevalResult(p.splinePointPercents[n]); point.position = evalResult.position; GetevalResult(p.primaryTangentPercents[n]); point.tangent = evalResult.position; GetevalResult(p.secondaryTangentPercents[n]); point.tangent2 = evalResult.position; switch (axis) { case Axis.X: point.normal = Quaternion.LookRotation(evalResult.forward, evalResult.up) * Quaternion.FromToRotation(Vector3.up, evalResult.up) * point.normal; break; case Axis.Y: point.normal = Quaternion.LookRotation(evalResult.forward, evalResult.up) * Quaternion.FromToRotation(Vector3.up, evalResult.up) * point.normal; break; case Axis.Z: point.normal = Quaternion.LookRotation(evalResult.forward, evalResult.up) * point.normal; break; } p.destinationSpline.points[n] = point; } } } void BendMesh(Vector3[] vertexPercents, Vector3[] originalNormals, TS_Mesh mesh, Matrix4x4 worldToLocalMatrix) { if(mesh.vertexCount != vertexPercents.Length) { Debug.LogError("Vertex count mismatch"); return; } for (int i = 0; i < mesh.vertexCount; i++) { Vector3 percent = vertexPercents[i]; if (axis == Axis.Y) percent.z = 1f - percent.z; GetevalResult(percent); mesh.vertices[i] = worldToLocalMatrix.MultiplyPoint3x4(evalResult.position); mesh.normals[i] = worldToLocalMatrix.MultiplyVector(normalMatrix.MultiplyVector(originalNormals[i])); } } protected override void PostBuild() { base.PostBuild(); if (!_bend) return; for (int i = 0; i < bendProperties.Length; i++) { bendProperties[i].Apply(i > 0 || trs != spline.transform); bendProperties[i].Update(); } } protected override void LateRun() { base.LateRun(); for (int i = 0; i < bendProperties.Length; i++) { bendProperties[i].Update(); } } [System.Serializable] public class BendProperty { public bool enabled = true; public bool isValid { get { return transform != null && transform.transform != null; } } public TS_Transform transform; public bool applyRotation = true; public bool applyScale = true; public bool bendMesh { get { return _bendMesh; } set { if (value != _bendMesh) { _bendMesh = value; if (value) { if (filter != null && filter.sharedMesh != null) { normals = originalMesh.normals; for (int i = 0; i < normals.Length; i++) normals[i] = transform.transform.TransformDirection(normals[i]); } } else RevertMesh(); } } } public bool generateLightmapUVs = false; public bool bendCollider { get { return _bendCollider; } set { if (value != _bendCollider) { _bendCollider = value; if (value) { if (collider != null && collider.sharedMesh != null && collider.sharedMesh != originalMesh) colliderNormals = originalColliderMesh.normals; } else RevertCollider(); } } } public bool bendSpline { get { return _bendSpline; } set { _bendSpline = value; if (value) { } } } [SerializeField] [HideInInspector] private bool _bendMesh = true; [SerializeField] [HideInInspector] private bool _bendSpline = true; [SerializeField] [HideInInspector] private bool _bendCollider = true; private float colliderUpdateDue = 0f; public float colliderUpdateRate = 0.2f; private bool updateCollider = false; public Vector3 originalPosition = Vector3.zero; public Vector3 originalScale = Vector3.one; public Quaternion originalRotation = Quaternion.identity; public Quaternion parentRotation = Quaternion.identity; public Vector3 positionPercent; public Vector3[] vertexPercents = new Vector3[0]; public Vector3[] normals = new Vector3[0]; public Vector3[] colliderVertexPercents = new Vector3[0]; public Vector3[] colliderNormals = new Vector3[0]; [SerializeField] [HideInInspector] private Mesh originalMesh = null; [SerializeField] [HideInInspector] private Mesh originalColliderMesh = null; private Spline _originalSpline; [SerializeField] [HideInInspector] private Mesh destinationMesh = null; [SerializeField] [HideInInspector] private Mesh destinationColliderMesh = null; public Spline destinationSpline; public TS_Mesh editMesh { get { if (!bendMesh || originalMesh == null) _editMesh = null; else if (_editMesh == null && originalMesh != null) _editMesh = new TS_Mesh(originalMesh); return _editMesh; } } public TS_Mesh editColliderMesh { get { if (!bendCollider || originalColliderMesh == null) _editColliderMesh = null; else if (_editColliderMesh == null && originalColliderMesh != null && originalColliderMesh != originalMesh) _editColliderMesh = new TS_Mesh(originalColliderMesh); return _editColliderMesh; } } public Spline originalSpline { get { if (!bendSpline || splineComputer == null) _originalSpline = null; else if (_originalSpline == null && splineComputer != null) { _originalSpline = new Spline(splineComputer.type); _originalSpline.points = splineComputer.GetPoints(); } return _originalSpline; } } public TS_Mesh _editMesh = null; public TS_Mesh _editColliderMesh = null; public MeshFilter filter = null; public MeshCollider collider = null; public SplineComputer splineComputer = null; public Vector3[] splinePointPercents = new Vector3[0]; public Vector3[] primaryTangentPercents = new Vector3[0]; public Vector3[] secondaryTangentPercents = new Vector3[0]; [SerializeField] [HideInInspector] private bool parent = false; public bool isParent { get { return parent; } } public BendProperty(Transform t, bool parent = false) { this.parent = parent; transform = new TS_Transform(t); originalPosition = t.localPosition; originalScale = t.localScale; originalRotation = t.localRotation; parentRotation = t.transform.rotation; if (t.transform.parent != null) parentRotation = t.transform.parent.rotation; filter = t.GetComponent(); collider = t.GetComponent(); if (filter != null && filter.sharedMesh != null) { originalMesh = filter.sharedMesh; normals = originalMesh.normals; for (int i = 0; i < normals.Length; i++) normals[i] = transform.transform.TransformDirection(normals[i]).normalized; } if (collider != null && collider.sharedMesh != null) { originalColliderMesh = collider.sharedMesh; colliderNormals = originalColliderMesh.normals; for (int i = 0; i < colliderNormals.Length; i++) colliderNormals[i] = transform.transform.TransformDirection(colliderNormals[i]); } if (!parent) splineComputer = t.GetComponent(); if (splineComputer != null) { if (splineComputer.isClosed) originalSpline.Close(); destinationSpline = new Spline(originalSpline.type); destinationSpline.points = new SplinePoint[originalSpline.points.Length]; destinationSpline.points = splineComputer.GetPoints(); if (splineComputer.isClosed) destinationSpline.Close(); } } public void Revert() { if (!isValid) return; RevertTransform(); RevertCollider(); RevertMesh(); if (splineComputer != null) splineComputer.SetPoints(_originalSpline.points); } private void RevertMesh() { if (filter != null) filter.sharedMesh = originalMesh; destinationMesh = null; } private void RevertTransform() { #if UNITY_EDITOR if (!Application.isPlaying) { transform.transform.localPosition = originalPosition; transform.transform.localRotation = originalRotation; } else { transform.localPosition = originalPosition; transform.localRotation = originalRotation; transform.Update(); } #else transform.localPosition = originalPosition; transform.localRotation = originalRotation; transform.Update(); #endif transform.scale = originalScale; transform.Update(); } private void RevertCollider() { if (collider != null) collider.sharedMesh = originalColliderMesh; destinationColliderMesh = null; } public void Apply(bool applyTransform) { if (!enabled) return; if (!isValid) return; if(applyTransform) transform.Update(); if (editMesh != null && editMesh.hasUpdate) ApplyMesh(); if (bendCollider && collider != null) { if (!updateCollider) { if((editColliderMesh == null && editMesh != null) || editColliderMesh != null) { updateCollider = true; if(Application.isPlaying) colliderUpdateDue = Time.time + colliderUpdateRate; } } } if (splineComputer != null) ApplySpline(); } public void Update() { if (Time.time >= colliderUpdateDue && updateCollider) { updateCollider = false; ApplyCollider(); } } private void ApplyMesh() { if (filter == null) return; MeshUtility.CalculateTangents(editMesh); if (destinationMesh == null) { destinationMesh = new Mesh(); destinationMesh.name = originalMesh.name; } editMesh.WriteMesh(ref destinationMesh); destinationMesh.RecalculateBounds(); filter.sharedMesh = destinationMesh; } private void ApplyCollider() { if (collider == null) return; if (originalColliderMesh == originalMesh) collider.sharedMesh = filter.sharedMesh; //if the collider has the same mesh as the filter - just copy it else { MeshUtility.CalculateTangents(editColliderMesh); if (destinationColliderMesh == null) { destinationColliderMesh = new Mesh(); destinationColliderMesh.name = originalColliderMesh.name; } editColliderMesh.WriteMesh(ref destinationColliderMesh); destinationColliderMesh.RecalculateBounds(); collider.sharedMesh = destinationColliderMesh; } } private void ApplySpline() { if (destinationSpline == null) return; splineComputer.SetPoints(destinationSpline.points); } } } }