using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Profiling; using WSMGameStudio.TerrainTools; namespace WSMGameStudio.Splines { [RequireComponent(typeof(LODGroup))] //Fix for Inspector performance issues on Unity 2018 public class Spline : MonoBehaviour { [SerializeField] private float _newCurveLength = 15f; [Range(0f, 90f)] [SerializeField] private float _newCurveAngle = 90; [SerializeField] private SMR_Theme _theme; [SerializeField] private Vector3[] _controlPointsPositions; //Local positions [SerializeField] private Quaternion[] _controlPointsRotations; [SerializeField] private BezierHandlesAlignment[] _modes; [SerializeField] private Vector3[] _controlPointsNormals; [SerializeField] private bool _loop; [SerializeField] private bool _staticSpline = true; [SerializeField] private bool _followTerrain = false; [SerializeField] private float _terrainCheckDistance = 20f; [SerializeField] private int _terraformingWidth = 3; [SerializeField] private int _embankmentWidth = 3; [SerializeField] private AnimationCurve _embankmentSlope = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); [SerializeField] Texture2D _terraformingTexture; [SerializeField] private float _minTextureBlending = 0.4f; [SerializeField] private float _maxTextureBlending = 0.6f; [SerializeField] [Range(0f, 1f)] private float _autoHandleSpacing = 0.33f; [SerializeField] private HandlesVisibility _handlesVisibility; [SerializeField] private Vector3Axis _flattenAxis = Vector3Axis.y; [SerializeField] private Vector2 _parallelSplineDirection = new Vector2(5f, 0f); private Transform _transform; private Vector3 _lastTransformPosition; private Quaternion _lastTransformRotation; private OrientedPoint[] _orientedPoints; //This CANNOT be serialized for performance sake, and it doesn't need to. (World positions) private float _terraformingStepsCount; private float _terraformingCurrentStep; #region PROPERTIES public Vector3[] ControlPointsPositions { get { return _controlPointsPositions; } set { _controlPointsPositions = value; } } public Quaternion[] ControlPointsRotations { get { return _controlPointsRotations; } set { _controlPointsRotations = value; } } public BezierHandlesAlignment[] Modes { get { return _modes; } set { _modes = value; } } public Vector3[] ControlPointsNormals { get { ValidateNormals(); return _controlPointsNormals; } set { _controlPointsNormals = value; } } /// /// Merge the first and last control points creating a continuos loop /// public bool Loop { get { return _loop; } set { if (_loop != value) { _loop = value; EnforceLoop(); } } } public float NewCurveLength { get { return _newCurveLength; } set { _newCurveLength = Mathf.Abs(value); _newCurveLength = _newCurveLength < 1 ? 1 : _newCurveLength; } } public float NewCurveAngle { get { return _newCurveAngle; } set { _newCurveAngle = (float)Math.Round(Mathf.Abs(value), 2, MidpointRounding.AwayFromZero); } } public bool StaticSpline { get { return _staticSpline; } set { _staticSpline = value; } } public bool FollowTerrain { get { return _followTerrain; } set { _followTerrain = value; } } public float TerrainCheckDistance { get { return _terrainCheckDistance; } set { _terrainCheckDistance = value; } } public int TerraformingWidth { get { return _terraformingWidth; } set { _terraformingWidth = value < 0 ? 0 : value; } } public int EmbankmentWidth { get { return _embankmentWidth; } set { _embankmentWidth = value < 0 ? 0 : value; } } public AnimationCurve EmbankmentSlope { get { return _embankmentSlope; } set { _embankmentSlope = value; } } public Texture2D TerraformingTexture { get { return _terraformingTexture; } set { _terraformingTexture = value; } } public float MinTextureBlending { get { return _minTextureBlending; } set { _minTextureBlending = Mathf.Clamp(value, 0f, 0.5f); } } public float MaxTextureBlending { get { return _maxTextureBlending; } set { _maxTextureBlending = Mathf.Clamp(value, 0.5f, 1f); } } public float AutoHandleSpacing { get { return _autoHandleSpacing; } set { if (value != _autoHandleSpacing) { _autoHandleSpacing = value; UpdateAllHandlesAligment(); } } } public HandlesVisibility HandlesVisibility { get { return _handlesVisibility; } set { _handlesVisibility = value; } } public Vector3Axis FlattenAxis { get { return _flattenAxis; } set { _flattenAxis = value; } } public Vector2 ParallelSplineDirection { get { return _parallelSplineDirection; } set { _parallelSplineDirection = value; } } public bool ZoomOnClick { get { if (_theme != null) return _theme.zoomOnClick; else return true; } set { if (_theme != null) _theme.zoomOnClick = value; } } public int ZoomLevel { get { if (_theme != null) return _theme.zoomLevel; else return 1; } set { if (_theme != null) _theme.zoomLevel = Math.Abs(value) < 1 ? 1 : Math.Abs(value); } } public Color SplineColor { get { if (_theme != null) return _theme.splineColor; else return Color.white; } set { if (_theme != null) _theme.splineColor = value; } } public bool ShowShortcutMenu { get { if (_theme != null) return _theme.showShortcutMenu; else return true; } set { if (_theme != null) _theme.showShortcutMenu = value; } } public bool SupportsVerticalBuilding { get { if (_theme != null) return _theme.supportsVerticalBuilding; else return true; } set { if (_theme != null) _theme.supportsVerticalBuilding = value; } } public int ControlPointCount { get { return _controlPointsPositions == null ? 0 : _controlPointsPositions.Length; } } public int CurveCount { get { return (_controlPointsPositions == null ? 0 : _controlPointsPositions.Length - 1) / 3; } } public float Length { get { return GetTotalDistance(); } } public OrientedPoint[] OrientedPoints { get { return _orientedPoints; } set { _orientedPoints = value; } } public SMR_Theme Theme { get { return _theme; } set { _theme = value; } } private float TerraformingProgress { get { return _terraformingStepsCount > 0f ? (_terraformingCurrentStep / _terraformingStepsCount) : 0f; } } #endregion private void OnEnable() { _transform = GetComponent(); _lastTransformPosition = _transform.position; _lastTransformRotation = _transform.rotation; ValidateNormals(); } /// /// Get control point by index /// /// /// public Vector3 GetControlPointPosition(int index) { if (_controlPointsPositions == null) Reset(); return _controlPointsPositions[index]; } /// /// Get rotation by index /// /// /// public Quaternion GetControlPointRotation(int index) { return _controlPointsRotations[index]; } /// /// Set control point rotation /// /// /// /// public void SetControlPointRotation(int index, Quaternion rotation) { if (index % 3 == 0) { Quaternion deltaRotation = rotation * Quaternion.Inverse(_controlPointsRotations[index]); if (_loop) { if (index == 0) { _controlPointsRotations[1] *= deltaRotation; _controlPointsRotations[_controlPointsRotations.Length - 2] *= deltaRotation; _controlPointsRotations[_controlPointsRotations.Length - 1] = rotation; } else if (index == _controlPointsPositions.Length - 1) { _controlPointsRotations[0] = rotation; _controlPointsRotations[1] *= deltaRotation; _controlPointsRotations[index - 1] *= deltaRotation; } else { _controlPointsRotations[index - 1] *= deltaRotation; _controlPointsRotations[index + 1] *= deltaRotation; } } else { if (index > 0) { _controlPointsRotations[index - 1] *= deltaRotation; } if (index + 1 < _controlPointsRotations.Length) { _controlPointsRotations[index + 1] *= deltaRotation; } } } _controlPointsRotations[index] = rotation; } /// /// Set control point by index /// /// /// public void SetControlPointPosition(int index, Vector3 point) { if (index % 3 == 0) { Vector3 deltaPosition = point - _controlPointsPositions[index]; if (_loop) { if (index == 0) { _controlPointsPositions[1] += deltaPosition; _controlPointsPositions[_controlPointsPositions.Length - 2] += deltaPosition; _controlPointsPositions[_controlPointsPositions.Length - 1] = point; } else if (index == _controlPointsPositions.Length - 1) { _controlPointsPositions[0] = point; _controlPointsPositions[1] += deltaPosition; _controlPointsPositions[index - 1] += deltaPosition; } else { _controlPointsPositions[index - 1] += deltaPosition; _controlPointsPositions[index + 1] += deltaPosition; } } else { if (index > 0) { _controlPointsPositions[index - 1] += deltaPosition; } if (index + 1 < _controlPointsPositions.Length) { _controlPointsPositions[index + 1] += deltaPosition; } } } _controlPointsPositions[index] = point; if (index % 3 == 0) { for (int i = (index - 3); i <= (index + 3); i = i + 3) { if (i >= 0 && i < _controlPointsPositions.Length) { if (_modes[i / 3] == BezierHandlesAlignment.Automatic || index == i) EnforceHandleAlignment(i); } } } else EnforceHandleAlignment(index); } /// /// Get control point mode by index /// /// /// public BezierHandlesAlignment GetHandlesAlignment(int index) { return _modes[(index + 1) / 3]; } /// /// Set control point mode /// /// /// public void SetHandlesAlignment(int index, BezierHandlesAlignment handleAlignment, bool enforceMode) { int modeIndex = (index + 1) / 3; _modes[modeIndex] = handleAlignment; if (_loop) { if (modeIndex == 0) _modes[_modes.Length - 1] = handleAlignment; else if (modeIndex == _modes.Length - 1) _modes[0] = handleAlignment; } if (enforceMode) EnforceHandleAlignment(index); } /// /// Get control point /// /// /// public Vector3 GetControlPointNormal(int index) { ValidateNormals(); return _controlPointsNormals[(index + 1) / 3]; } /// /// Set control point normal /// /// /// public void SetControlPointNormal(int index, Vector3 normal) { ValidateNormals(); int normalIndex = (index + 1) / 3; _controlPointsNormals[normalIndex] = normal.normalized; if (_loop) { if (normalIndex == 0) _controlPointsNormals[_controlPointsNormals.Length - 1] = normal.normalized; else if (normalIndex == _controlPointsNormals.Length - 1) _controlPointsNormals[0] = normal.normalized; } } /// /// Update all handles aligment /// private void UpdateAllHandlesAligment() { for (int i = 0; i < _modes.Length; i++) { EnforceHandleAlignment(i * 3); } } /// /// Make sure the selected control point handles alignment mode is applied /// /// private void EnforceHandleAlignment(int index) { int alignmentIndex = (index + 1) / 3; BezierHandlesAlignment handleAlignment = _modes[alignmentIndex]; if (handleAlignment == BezierHandlesAlignment.Free) // Don't align if free mode is selected return; // Control point index is always in the middle of two handles int controlPointIndex = alignmentIndex * 3; if (handleAlignment == BezierHandlesAlignment.Automatic) { int previousControlPointIndex, nextControlPointIndex, previousHandle, nextHandle, lookTargetHandle; Vector3 direction, prevDirection, nextDirection; float previousNeighbourDistance, nextNeighbourDistance; if (controlPointIndex == 0) // First { if (_loop) { previousControlPointIndex = _controlPointsPositions.Length - 3; previousHandle = _controlPointsPositions.Length - 2; prevDirection = (_controlPointsPositions[previousControlPointIndex] - _controlPointsPositions[controlPointIndex]).normalized; previousNeighbourDistance = Vector3.Distance(_controlPointsPositions[controlPointIndex], _controlPointsPositions[previousControlPointIndex]); nextControlPointIndex = controlPointIndex + 3; nextHandle = controlPointIndex + 1; nextDirection = (_controlPointsPositions[nextControlPointIndex] - _controlPointsPositions[controlPointIndex]).normalized; nextNeighbourDistance = Vector3.Distance(_controlPointsPositions[controlPointIndex], _controlPointsPositions[nextControlPointIndex]); direction = (nextDirection - prevDirection).normalized; _controlPointsPositions[previousHandle] = _controlPointsPositions[controlPointIndex] - direction * previousNeighbourDistance * _autoHandleSpacing; _controlPointsPositions[nextHandle] = _controlPointsPositions[controlPointIndex] + direction * nextNeighbourDistance * _autoHandleSpacing; } else { nextControlPointIndex = controlPointIndex + 3; nextHandle = controlPointIndex + 1; lookTargetHandle = controlPointIndex + 2; direction = _controlPointsPositions[lookTargetHandle] - _controlPointsPositions[controlPointIndex]; direction = direction / direction.magnitude; nextNeighbourDistance = Vector3.Distance(_controlPointsPositions[controlPointIndex], _controlPointsPositions[nextControlPointIndex]); _controlPointsPositions[nextHandle] = _controlPointsPositions[controlPointIndex] + direction * nextNeighbourDistance * _autoHandleSpacing; } } else if (controlPointIndex == _controlPointsPositions.Length - 1) // Last { if (_loop) { previousControlPointIndex = _controlPointsPositions.Length - 3; previousHandle = _controlPointsPositions.Length - 2; prevDirection = (_controlPointsPositions[previousControlPointIndex] - _controlPointsPositions[controlPointIndex]).normalized; previousNeighbourDistance = Vector3.Distance(_controlPointsPositions[controlPointIndex], _controlPointsPositions[previousControlPointIndex]); nextControlPointIndex = 3; nextHandle = 1; nextDirection = (_controlPointsPositions[nextControlPointIndex] - _controlPointsPositions[controlPointIndex]).normalized; nextNeighbourDistance = Vector3.Distance(_controlPointsPositions[controlPointIndex], _controlPointsPositions[nextControlPointIndex]); direction = (nextDirection - prevDirection).normalized; _controlPointsPositions[previousHandle] = _controlPointsPositions[controlPointIndex] - direction * previousNeighbourDistance * _autoHandleSpacing; _controlPointsPositions[nextHandle] = _controlPointsPositions[controlPointIndex] + direction * nextNeighbourDistance * _autoHandleSpacing; } else { previousControlPointIndex = controlPointIndex - 3; previousHandle = controlPointIndex - 1; lookTargetHandle = controlPointIndex - 2; direction = _controlPointsPositions[controlPointIndex] - _controlPointsPositions[lookTargetHandle]; direction = direction / direction.magnitude; previousNeighbourDistance = Vector3.Distance(_controlPointsPositions[controlPointIndex], _controlPointsPositions[previousControlPointIndex]); _controlPointsPositions[previousHandle] = _controlPointsPositions[controlPointIndex] - direction * previousNeighbourDistance * _autoHandleSpacing; } } else // In between { previousControlPointIndex = _loop ? LoopIndexAroundArray(_controlPointsPositions, controlPointIndex - 3) : controlPointIndex - 3; previousHandle = _loop ? LoopIndexAroundArray(_controlPointsPositions, controlPointIndex - 1) : controlPointIndex - 1; prevDirection = (_controlPointsPositions[previousControlPointIndex] - _controlPointsPositions[controlPointIndex]).normalized; previousNeighbourDistance = Vector3.Distance(_controlPointsPositions[controlPointIndex], _controlPointsPositions[previousControlPointIndex]); nextControlPointIndex = _loop ? LoopIndexAroundArray(_controlPointsPositions, controlPointIndex + 3) : controlPointIndex + 3; nextHandle = _loop ? LoopIndexAroundArray(_controlPointsPositions, controlPointIndex + 1) : controlPointIndex + 1; nextDirection = (_controlPointsPositions[nextControlPointIndex] - _controlPointsPositions[controlPointIndex]).normalized; nextNeighbourDistance = Vector3.Distance(_controlPointsPositions[controlPointIndex], _controlPointsPositions[nextControlPointIndex]); direction = (nextDirection - prevDirection).normalized; _controlPointsPositions[previousHandle] = _controlPointsPositions[controlPointIndex] - direction * previousNeighbourDistance * _autoHandleSpacing; _controlPointsPositions[nextHandle] = _controlPointsPositions[controlPointIndex] + direction * nextNeighbourDistance * _autoHandleSpacing; } } else // Enforce Aligned and Mirrored modes { // Don't align if it is start or end of non-loop spline if (!_loop && (alignmentIndex == 0 || alignmentIndex == _modes.Length - 1)) return; int fixedHandleIndex, enforcedHandleIndex; //Verifying which handle should be fixed and which should be enforced if (index <= controlPointIndex) { fixedHandleIndex = controlPointIndex - 1; if (fixedHandleIndex < 0) fixedHandleIndex = _controlPointsPositions.Length - 2; enforcedHandleIndex = controlPointIndex + 1; if (enforcedHandleIndex >= _controlPointsPositions.Length) enforcedHandleIndex = 1; } else { fixedHandleIndex = controlPointIndex + 1; if (fixedHandleIndex >= _controlPointsPositions.Length) fixedHandleIndex = 1; enforcedHandleIndex = controlPointIndex - 1; if (enforcedHandleIndex < 0) enforcedHandleIndex = _controlPointsPositions.Length - 2; } Vector3 middle = _controlPointsPositions[controlPointIndex]; Vector3 enforcedTangent = middle - _controlPointsPositions[fixedHandleIndex]; if (handleAlignment == BezierHandlesAlignment.Aligned) { enforcedTangent = enforcedTangent.normalized * Vector3.Distance(middle, _controlPointsPositions[enforcedHandleIndex]); } _controlPointsPositions[enforcedHandleIndex] = middle + enforcedTangent; } } /// /// Loop index around array if out of bounds /// /// /// /// /// private int LoopIndexAroundArray(T[] array, int index) { return (index + array.Length) % array.Length; } /// /// Get curve starting point index /// /// /// public int GetCurveStartingPointIndex(int orientedPointIndex) { float t = orientedPointIndex / (OrientedPoints.Length - 1f); return GetCurveStartingPointIndex(t); } /// /// Get curve starting point index /// /// /// public int GetCurveStartingPointIndex(float t) { int curveStartIndex; if (t >= 1f) { t = 1f; curveStartIndex = _controlPointsPositions.Length - 4; } else { t = Mathf.Clamp01(t) * CurveCount; curveStartIndex = (int)t; t -= curveStartIndex; curveStartIndex *= 3; } return curveStartIndex; } /// /// Return the index of the closest oriented point /// /// Percentual interpolotion value /// public int GetClosestOrientedPointIndex(float t, bool alwaysRoundToLowerIndex = true) { int closest = 0; t = Mathf.Clamp01(t); if (_orientedPoints == null || _orientedPoints.Length == 0) CalculateOrientedPoints(1f); if (alwaysRoundToLowerIndex) closest = (int)((_orientedPoints.Length - 1) * t); else closest = Mathf.RoundToInt((_orientedPoints.Length - 1) * t); return closest; } /// /// Get point (world space) /// /// /// public Vector3 GetPoint(float t) { int curveStartIndex; if (t >= 1f) { t = 1f; curveStartIndex = _controlPointsPositions.Length - 4; } else { t = Mathf.Clamp01(t) * CurveCount; curveStartIndex = (int)t; t -= curveStartIndex; curveStartIndex *= 3; } return transform.TransformPoint(Bezier.GetPoint( _controlPointsPositions[curveStartIndex], _controlPointsPositions[curveStartIndex + 1], _controlPointsPositions[curveStartIndex + 2], _controlPointsPositions[curveStartIndex + 3], t)); } /// /// Get point on segment /// /// /// /// /// /// /// public Vector3 GetPoint(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) { return Bezier.GetPoint(p0, p1, p2, p3, t); } /// /// Calculate normal /// /// /// public Vector3 GetNormal(float t) { int curveStartIndex = GetCurveStartingPointIndex(t); int curveEndIndex = curveStartIndex + 3; return transform.TransformDirection(Vector3.Lerp(GetControlPointNormal(curveStartIndex), GetControlPointNormal(curveEndIndex), t).normalized); } /// /// Get normal on segment /// /// /// /// /// public Vector3 GetNormal(Vector3 n1, Vector3 n2, float t) { return transform.TransformDirection(Vector3.Lerp(n1, n2, t).normalized); } /// /// Get point rotation at spline postion t /// /// /// public Quaternion GetRotation(float t) { int curveStartIndex; if (t >= 1f) { t = 1f; curveStartIndex = _controlPointsRotations.Length - 4; } else { t = Mathf.Clamp01(t) * CurveCount; curveStartIndex = (int)t; t -= curveStartIndex; curveStartIndex *= 3; } return Bezier.GetPointRotation(_controlPointsRotations[curveStartIndex], _controlPointsRotations[curveStartIndex + 1], _controlPointsRotations[curveStartIndex + 2], _controlPointsRotations[curveStartIndex + 3], t); } /// /// Get velocity /// /// /// public Vector3 GetVelocity(float t) { int curveStartIndex; if (t >= 1f) { t = 1f; curveStartIndex = _controlPointsPositions.Length - 4; } else { t = Mathf.Clamp01(t) * CurveCount; curveStartIndex = (int)t; t -= curveStartIndex; curveStartIndex *= 3; } return GetVelocity(_controlPointsPositions[curveStartIndex], _controlPointsPositions[curveStartIndex + 1], _controlPointsPositions[curveStartIndex + 2], _controlPointsPositions[curveStartIndex + 3], t); } /// /// Get velocity on spline segment /// /// /// /// /// /// /// public Vector3 GetVelocity(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) { Vector3 firstDerivative = Bezier.GetFirstDerivative(p0, p1, p2, p3, t); return transform.TransformPoint(firstDerivative) - transform.position; } /// /// Get direction /// /// /// public Vector3 GetDirection(float t) { return GetVelocity(t).normalized; } /// /// Get direction on spline segment /// /// /// /// /// /// /// public Vector3 GetDirection(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) { return GetVelocity(p0, p1, p2, p3, t).normalized; } /// /// Get a list of spline oriented points based on the number os steps /// /// /// public List GetOrientedPoints(int steps) { List ret = new List(); float stepPercentage = 1f / steps; float t = 0; while (t < 1f) { OrientedPoint orientedPoint = GetOrientedPoint(t); ret.Add(orientedPoint); t += stepPercentage; } return ret; } /// /// Get point position and rotation on spline position t /// /// /// public OrientedPoint GetOrientedPoint(float t) { Vector3 position = GetPoint(t); Quaternion rotation = GetRotation(t); Vector3 direction = GetDirection(t); Vector3 normal = GetNormal(t); rotation = rotation * Quaternion.LookRotation(direction, normal); normal = rotation * Vector3.up; return new OrientedPoint(position, rotation, direction, normal); } /// /// Get position and rotation on a spline segment /// /// /// /// /// /// /// public OrientedPoint GetOrientedPoint(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, Quaternion r0, Quaternion r1, Quaternion r2, Quaternion r3, Vector3 n1, Vector3 n2, float t) { Vector3 position = GetPoint(p0, p1, p2, p3, t); Quaternion rotation = Bezier.GetPointRotation(r0, r1, r2, r3, t); Vector3 direction = GetDirection(p0, p1, p2, p3, t); Vector3 normal = GetNormal(n1, n2, t); rotation = rotation * Quaternion.LookRotation(direction, normal); normal = rotation * Vector3.up; return new OrientedPoint(position, rotation, direction, normal); } /// /// Reset spline /// public void Reset() { _loop = false; _handlesVisibility = HandlesVisibility.ShowOnlyActiveHandles; _controlPointsPositions = new Vector3[4]; _controlPointsRotations = new Quaternion[4]; _modes = new BezierHandlesAlignment[2]; _controlPointsNormals = new Vector3[2]; for (int i = 0; i < _controlPointsPositions.Length; i++) { _controlPointsPositions[i] = new Vector3(0f, 0f, i * (_newCurveLength / 3)); _controlPointsRotations[i] = Quaternion.identity; } for (int i = 0; i < _modes.Length; i++) { _modes[i] = BezierHandlesAlignment.Aligned; _controlPointsNormals[i] = Vector3.up; } } /// /// Reset normals array to default value /// public void ResetNormals() { int normalsCount = _modes.Length; _controlPointsNormals = new Vector3[normalsCount]; ResetNormals(Vector3.up); ResetRotations(); } /// /// Set all control points normals to selected value /// /// private void ResetNormals(Vector3 newNormal) { for (int i = 0; i < _controlPointsNormals.Length; i++) { _controlPointsNormals[i] = newNormal; } } /// /// Make sure normals array is not null (backwards compatibility) /// private void ValidateNormals() { if (_controlPointsNormals == null || _controlPointsNormals.Length == 0 || _controlPointsNormals.Length != _modes.Length) ResetNormals(); } /// /// Reset all control points rotations /// private void ResetRotations() { ResetRotations(Quaternion.identity); } /// /// Set all control points rotations to new rotation value /// private void ResetRotations(Quaternion newRotation) { for (int i = 0; i < _controlPointsRotations.Length; i++) { _controlPointsRotations[i] = newRotation; } } /// /// Add new curve to spline /// public void AddCurve() { ValidateNormals(); if (_loop) _loop = false; //Add positions Vector3 lastPointPosition = _controlPointsPositions[_controlPointsPositions.Length - 1]; Vector3 lastPointDirection = transform.InverseTransformDirection(GetDirection(1f)); // Last point direction Quaternion lastPointRotation = GetRotation(1f); // Last point rotation Array.Resize(ref _controlPointsPositions, _controlPointsPositions.Length + 3); Array.Resize(ref _controlPointsRotations, _controlPointsRotations.Length + 3); float positionOffset = (_newCurveLength / 3); //Add the 3 new control points for (int i = 3; i > 0; i--) { //Calculate new position based on last point direction lastPointPosition += (lastPointDirection * positionOffset); //Position _controlPointsPositions[_controlPointsPositions.Length - i] = lastPointPosition; //Rotation _controlPointsRotations[_controlPointsRotations.Length - i] = lastPointRotation; } //Add normal Array.Resize(ref _controlPointsNormals, _controlPointsNormals.Length + 1); _controlPointsNormals[_controlPointsNormals.Length - 1] = _controlPointsNormals[_controlPointsNormals.Length - 2]; //Add modes Array.Resize(ref _modes, _modes.Length + 1); _modes[_modes.Length - 1] = _modes[_modes.Length - 2]; EnforceHandleAlignment(_controlPointsPositions.Length - 4); if (_loop) { _controlPointsPositions[_controlPointsPositions.Length - 1] = _controlPointsPositions[0]; _controlPointsRotations[_controlPointsRotations.Length - 1] = _controlPointsRotations[0]; _modes[_modes.Length - 1] = _modes[0]; EnforceHandleAlignment(0); } } /// /// Add new curve to spline based on target point /// /// world space point public void AddCurve(Vector3 point, Vector3 upwardsDirection) { AddCurve(); int lastPointIndex = _controlPointsPositions.Length - 1; int lastPointHandleIndex = _controlPointsPositions.Length - 2; int startPointHandleIndex = _controlPointsPositions.Length - 3; int startPointIndex = _controlPointsPositions.Length - 4; Vector3 startPointDirection = GetCurveStartDirection(startPointIndex); Vector3 startPoint_Right = (Quaternion.LookRotation(startPointDirection, upwardsDirection) * Vector3.right); Vector3 startPoint_Up = (Quaternion.LookRotation(startPointDirection, upwardsDirection) * Vector3.up); point = transform.InverseTransformPoint(point); //Convert to local position _controlPointsPositions[lastPointIndex] = point; Vector3 targetPointDirection = (_controlPointsPositions[lastPointIndex] - _controlPointsPositions[startPointIndex]); targetPointDirection = (targetPointDirection / targetPointDirection.magnitude); float targetAngle = Vector3.SignedAngle(startPointDirection, targetPointDirection, startPoint_Up); /* * 0º -> 45º = back -> left * 45º -> 90º+ = left -> forward * 0º -> -45º = back -> right * -45º -> -90º- = right -> forward */ Vector3 dir1 = targetAngle > -45f && targetAngle <= 45f ? -startPointDirection : (targetAngle > 45f ? -startPoint_Right : startPoint_Right); Vector3 dir2 = targetAngle > 45f || targetAngle <= -45f ? startPointDirection : (targetAngle > 0f && targetAngle <= 45f ? -startPoint_Right : startPoint_Right); float absoluteaAngle = Mathf.Abs(targetAngle); float t = absoluteaAngle <= 45f ? (absoluteaAngle / 45f) : (absoluteaAngle - 45f) / 45f; Vector3 lastHandleDirection = Vector3.Lerp(dir1, dir2, t); float minHandleDistanceRatio = absoluteaAngle <= 45f ? 0.33f : 0.4f; float maxHandleDistanceRatio = absoluteaAngle <= 45f ? 0.4f : 0.67f; float handleDistanceRatio = Mathf.Lerp(minHandleDistanceRatio, maxHandleDistanceRatio, t); float distance = Vector3.Distance(_controlPointsPositions[startPointIndex], _controlPointsPositions[lastPointIndex]); _controlPointsPositions[startPointHandleIndex] = _controlPointsPositions[startPointIndex] + startPointDirection * distance * handleDistanceRatio; _controlPointsPositions[lastPointHandleIndex] = _controlPointsPositions[lastPointIndex] + lastHandleDirection * distance * handleDistanceRatio; _controlPointsRotations[lastPointIndex] = GetRotation(1f); } /// /// /// /// /// /// /// /// public void PreviewQuadraticBezierCurve(Vector3 point, out Vector3 startPoint, out Vector3 handle1, out Vector3 handle2, out Vector3 endPoint, Vector3 upwardsDirection) { int lastPointIndex = _controlPointsPositions.Length - 1; int startPointIndex = _controlPointsPositions.Length - 4; Vector3 startPointDirection = GetCurveEndDirection(startPointIndex); Vector3 startPoint_Right = (Quaternion.LookRotation(startPointDirection, upwardsDirection) * Vector3.right); Vector3 startPoint_Up = (Quaternion.LookRotation(startPointDirection, upwardsDirection) * Vector3.up); point = transform.InverseTransformPoint(point); //Convert to local position Vector3 targetPointDirection = (point - _controlPointsPositions[lastPointIndex]); targetPointDirection = (targetPointDirection / targetPointDirection.magnitude); float targetAngle = Vector3.SignedAngle(startPointDirection, targetPointDirection, startPoint_Up); Vector3 dir1 = targetAngle > -45f && targetAngle <= 45f ? -startPointDirection : (targetAngle > 45f ? -startPoint_Right : startPoint_Right); Vector3 dir2 = targetAngle > 45f || targetAngle <= -45f ? startPointDirection : (targetAngle > 0f && targetAngle <= 45f ? -startPoint_Right : startPoint_Right); float absoluteaAngle = Mathf.Abs(targetAngle); float t = absoluteaAngle <= 45f ? (absoluteaAngle / 45f) : (absoluteaAngle - 45f) / 45f; Vector3 lastHandleDirection = Vector3.Lerp(dir1, dir2, t); float minHandleDistanceRatio = absoluteaAngle <= 45f ? 0.33f : 0.4f; float maxHandleDistanceRatio = absoluteaAngle <= 45f ? 0.4f : 0.67f; float handleDistanceRatio = Mathf.Lerp(minHandleDistanceRatio, maxHandleDistanceRatio, t); float distance = Vector3.Distance(_controlPointsPositions[lastPointIndex], point); handle1 = _controlPointsPositions[lastPointIndex] + startPointDirection * distance * handleDistanceRatio; handle2 = point + lastHandleDirection * distance * handleDistanceRatio; _transform = _transform == null ? GetComponent() : _transform; startPoint = _transform.TransformPoint(_controlPointsPositions[lastPointIndex]); handle1 = _transform.TransformPoint(handle1); handle2 = _transform.TransformPoint(handle2); endPoint = _transform.TransformPoint(point); } /// /// Remove the last curve (Disables loop property) /// public void RemoveCurve() { ValidateNormals(); if (CurveCount <= 1) { Debug.Log("Spline has only one curve. Cannot remove last curve."); return; } _loop = false; Array.Resize(ref _controlPointsPositions, _controlPointsPositions.Length - 3); Array.Resize(ref _controlPointsRotations, _controlPointsRotations.Length - 3); Array.Resize(ref _modes, _modes.Length - 1); Array.Resize(ref _controlPointsNormals, _controlPointsNormals.Length - 1); } /// /// Curve spline on the desired direction based on the current "new curve angle" value /// /// public void ShapeCurve(Vector3 direction) { if (_transform == null) _transform = GetComponent(); ResetLastCurve(); //Straighten curve to get initial Slerp values // Disable automatic aligment to avoid shape deformation if (_modes[_modes.Length - 1] == BezierHandlesAlignment.Automatic) _modes[_modes.Length - 1] = BezierHandlesAlignment.Aligned; int curveStartIndex = _controlPointsPositions.Length - 4; // Avoid divided by zero error and keeps the straighted segment wich is expected result for 0º angle input if (_newCurveAngle == 0f) return; float angleRatio = _newCurveAngle / 360f; float circleRadius = _newCurveLength / (angleRatio * 2f * Mathf.PI); Vector3 startPointDirection = GetCurveStartDirection(curveStartIndex); Vector3 startPointNormal = GetControlPointNormal(curveStartIndex); Vector3 startPoint_Right = (Quaternion.LookRotation(startPointDirection, startPointNormal) * Vector3.right); Vector3 startPoint_Up = (Quaternion.LookRotation(startPointDirection, startPointNormal) * Vector3.up); Vector3 centerDirection = Vector3.zero; Vector3 circle_Up = Vector3.zero; if (direction == _transform.right) // Curve to the right { centerDirection = startPoint_Right; circle_Up = startPoint_Up; } else if (direction == -_transform.right) // Curve to the left { centerDirection = -startPoint_Right; circle_Up = -startPoint_Up; } else if (direction == _transform.up) // Curve up { centerDirection = startPoint_Up; circle_Up = -startPoint_Right; } else if (direction == -_transform.up) // Curve down { centerDirection = -startPoint_Up; circle_Up = startPoint_Right; } // Imaginary circle Vector3 circleStart = _controlPointsPositions[curveStartIndex]; Vector3 circleCenter = circleStart + (centerDirection * circleRadius); float t = _newCurveAngle / 90f; Vector3 intersectionDirection = Vector3.Slerp(-centerDirection, startPointDirection, t); Vector3 endPoint = circleCenter + (intersectionDirection * circleRadius); float handleDistanceRatio = Mathf.Lerp(0.33f, 0.55f, t); // Optimal handles distance for perfect circunference float refDistance = Mathf.Abs(Vector3.Distance(circleStart, endPoint) / 1.4142f); //Pitagoras teorem for calculating side distance of imaginary square Vector3 startPointHandle = circleStart + (startPointDirection * refDistance * handleDistanceRatio); Vector3 handleDirection = Vector3.Cross(intersectionDirection, circle_Up); Vector3 endPointHandle = endPoint + (handleDirection * refDistance * handleDistanceRatio); _controlPointsPositions[curveStartIndex + 1] = startPointHandle; _controlPointsPositions[curveStartIndex + 2] = endPointHandle; _controlPointsPositions[curveStartIndex + 3] = endPoint; } /// /// Reset last curve /// public void ResetLastCurve() { _loop = false; int curveStartIndex = _controlPointsPositions.Length - 4; Vector3 startPointPosition = _controlPointsPositions[curveStartIndex]; Vector3 startPointDirection = GetCurveStartDirection(curveStartIndex); Quaternion startPointRotation = GetCurveRotation(curveStartIndex); float positionOffset = (_newCurveLength / 3); //Reset control points positions and rotations for (int i = 3; i > 0; i--) { //Calculate new position based on last point direction startPointPosition += (startPointDirection * positionOffset); //Position _controlPointsPositions[_controlPointsPositions.Length - i] = startPointPosition; //Rotation _controlPointsRotations[_controlPointsRotations.Length - i] = startPointRotation; } } /// /// Get curve diretion at the start of the curve /// /// /// private Vector3 GetCurveStartDirection(int curveStartIndex) { return transform.InverseTransformDirection(GetDirection(_controlPointsPositions[curveStartIndex], _controlPointsPositions[curveStartIndex + 1], _controlPointsPositions[curveStartIndex + 2], _controlPointsPositions[curveStartIndex + 3], 0f)); } /// /// Get curve diretion at the end of the curve /// /// /// private Vector3 GetCurveEndDirection(int curveStartIndex) { return transform.InverseTransformDirection(GetDirection(_controlPointsPositions[curveStartIndex], _controlPointsPositions[curveStartIndex + 1], _controlPointsPositions[curveStartIndex + 2], _controlPointsPositions[curveStartIndex + 3], 1f)); } /// /// Get curve rotation at the start of the curve /// /// /// private Quaternion GetCurveRotation(int curveStartIndex) { return Bezier.GetPointRotation(_controlPointsRotations[curveStartIndex], _controlPointsRotations[curveStartIndex + 1], _controlPointsRotations[curveStartIndex + 2], _controlPointsRotations[curveStartIndex + 3], 0f); } /// /// Disable colliders and save their previous state /// /// /// private static bool[] DisableColliders(MeshCollider[] colliders) { bool[] collidersState = new bool[colliders.Length]; for (int i = 0; i < colliders.Length; i++) { collidersState[i] = colliders[i].enabled; colliders[i].enabled = false; } return collidersState; } /// /// Reenable colliders /// /// /// private static void RenableColliders(MeshCollider[] colliders, bool[] collidersState) { for (int i = 0; i < colliders.Length; i++) colliders[i].enabled = collidersState[i]; } /// /// Disable colliders and save their active previous state /// /// /// private static bool[] DisableIgnoredObjects(SMR_IgnoredObject[] objects) { bool[] objectsState = new bool[objects.Length]; for (int i = 0; i < objects.Length; i++) { objectsState[i] = objects[i].gameObject.activeInHierarchy; objects[i].gameObject.SetActive(false); } return objectsState; } /// /// Reenable objects that were enabled before /// /// /// private static void RenableIgnoredObjects(SMR_IgnoredObject[] objects, bool[] objectsState) { for (int i = 0; i < objects.Length; i++) objects[i].gameObject.SetActive(objectsState[i]); } /// /// Reset control points heights /// public void Flatten() { Vector3 firstPointPosition = GetControlPointPosition(0); // Flatten control points for (int i = 0; i < _controlPointsPositions.Length; i += 3) { FlattenPoint(firstPointPosition, i); } // Flaten handles for (int i = 0; i < _controlPointsPositions.Length; i++) { if (i % 3 == 0) continue; FlattenPoint(firstPointPosition, i); } UpdateMeshRenderer(false); } /// /// Flatten selected point based on flatten axis property /// /// /// private void FlattenPoint(Vector3 firstPointPosition, int pointIndex) { float x = _flattenAxis == Vector3Axis.x ? firstPointPosition.x : _controlPointsPositions[pointIndex].x; float y = _flattenAxis == Vector3Axis.y ? firstPointPosition.y : _controlPointsPositions[pointIndex].y; float z = _flattenAxis == Vector3Axis.z ? firstPointPosition.z : _controlPointsPositions[pointIndex].z; _controlPointsPositions[pointIndex] = new Vector3(x, y, z); } /// /// Updates Spline Mesh Renderer component (if any) /// /// Update immediately even when manual mesh generation is selected protected void UpdateMeshRenderer(bool forceUpdate) { SplineMeshRenderer splineMeshRenderer = GetComponent(); if (splineMeshRenderer != null) { if (forceUpdate || splineMeshRenderer.MeshGenerationMethod == MeshGenerationMethod.Realtime) splineMeshRenderer.ExtrudeMesh(); } } /// /// Return bezier curve aproximated Length in meters /// /// /// public float GetTotalDistance(bool realDistance = false) { float length = 0f; // Calculate real length based on oriented points if (realDistance) { _orientedPoints = (_orientedPoints == null || _orientedPoints.Length == 0) ? CalculateOrientedPoints(1f) : _orientedPoints; int penultimateIndex = (_orientedPoints.Length - 2); for (int i = 0; i <= penultimateIndex; i++) length += Vector3.Distance(_orientedPoints[i].Position, _orientedPoints[i + 1].Position); } else // Calculate aproximated length { for (float t = 0f; t < 1f; t += 0.1f) length += Vector3.Distance(GetPoint(t), GetPoint(t + 0.1f)); } return length; } /// /// Split spline in two using the selected control point as reference /// /// public void SplitSpline(int selectedIndex) { ValidateNormals(); _loop = false; // Disable loop on Split if (selectedIndex == 0) { SubdivideCurve(selectedIndex); selectedIndex = 3; } _transform = _transform == null ? GetComponent() : _transform; //Creating new spline control points int newSplineLength = _controlPointsPositions.Length - selectedIndex; int newModesLength = (newSplineLength / 3) + 1; Vector3[] newControlPointsPositions = new Vector3[newSplineLength]; Quaternion[] newControlPointsRotations = new Quaternion[newSplineLength]; Vector3[] newControlPointsNormals = new Vector3[newModesLength]; BezierHandlesAlignment[] newModes = new BezierHandlesAlignment[newModesLength]; //Populating new spline control points int newModesStart = (selectedIndex / 3); Array.Copy(_controlPointsPositions, selectedIndex, newControlPointsPositions, 0, newSplineLength); Array.Copy(_controlPointsRotations, selectedIndex, newControlPointsRotations, 0, newSplineLength); Array.Copy(_modes, newModesStart, newModes, 0, newModesLength); Array.Copy(_controlPointsNormals, newModesStart, newControlPointsNormals, 0, newModesLength); //Creating new spline game object GameObject newSplineGameObject = Instantiate(this.gameObject, this.transform.parent); Transform newSplineTransform = newSplineGameObject.GetComponent(); Spline newSpline = newSplineGameObject.GetComponent(); newSpline._controlPointsPositions = newControlPointsPositions; newSpline._controlPointsRotations = newControlPointsRotations; newSpline._modes = newModes; newSpline._controlPointsNormals = newControlPointsNormals; //Adjusting new spline world position and pivot Vector3 firstControlPoint = newSpline._controlPointsPositions[0]; //Look towards first handle to get spline correct rotation newSplineTransform.position = transform.TransformPoint(firstControlPoint); Vector3 lookTarget = transform.TransformPoint(newControlPointsPositions[1]); Vector3[] worldPos = new Vector3[newControlPointsPositions.Length]; //Save as world position to preserve relative position after rotation for (int i = 0; i < newSpline._controlPointsPositions.Length; i++) { newSpline._controlPointsPositions[i] -= firstControlPoint; worldPos[i] = newSplineTransform.TransformPoint(newSpline._controlPointsPositions[i]); } //Rotate spline newSplineTransform.LookAt(lookTarget); //Convert back to local positions for (int i = 0; i < newSpline._controlPointsPositions.Length; i++) { newSpline._controlPointsPositions[i] = newSplineTransform.InverseTransformPoint(worldPos[i]); } //Removing control points from older spline int resizeLength = selectedIndex + 1; int modeResizeLength = newModesStart + 1; Array.Resize(ref _controlPointsPositions, resizeLength); Array.Resize(ref _controlPointsRotations, resizeLength); Array.Resize(ref _modes, modeResizeLength); Array.Resize(ref _controlPointsNormals, modeResizeLength); UpdateMeshRenderer(true); newSpline.UpdateMeshRenderer(true); #if UNITY_EDITOR //Select new spline createdon Editor UnityEditor.Selection.activeGameObject = newSplineGameObject; EnforceUniqueName(newSpline.gameObject); #endif ValidateMeshRendererCaps(this.GetComponent(), newSpline.GetComponent()); Debug.Log("Spline Splitted Successfully!"); } /// /// Make sure new object has a unique name /// /// private static void EnforceUniqueName(GameObject targetObject) { #if UNITY_EDITOR targetObject.name = targetObject.name.Replace("(Clone)", string.Empty); UnityEditor.GameObjectUtility.EnsureUniqueNameForSibling(targetObject); #endif } /// /// Merge with target spline /// WARNING: target spline will be deleted after merging /// /// /// public void Merge(GameObject target, bool deleteTarget = true, bool disableTargetIfNotDeleted = true) { ValidateNormals(); Spline targetSpline = target.GetComponent(); if (targetSpline == null) { Debug.Log("Merging not possible, target GameObject is not a Spline"); return; } List mergedPoints = new List(); List mergedRotations = new List(); List mergedNormals = new List(); List mergedModes = new List(); int enforceAligmentIndex = _controlPointsPositions.Length - 1; mergedPoints.AddRange(_controlPointsPositions); mergedRotations.AddRange(_controlPointsRotations); mergedModes.AddRange(_modes); mergedNormals.AddRange(_controlPointsNormals); Vector3 worldPos; Transform targetTransform = targetSpline.transform; _transform = _transform == null ? GetComponent() : _transform; for (int pointIndex = 1; pointIndex < targetSpline.ControlPointsPositions.Length; pointIndex++) { worldPos = targetTransform.TransformPoint(targetSpline.ControlPointsPositions[pointIndex]); mergedPoints.Add(_transform.InverseTransformPoint(worldPos)); mergedRotations.Add(targetSpline.ControlPointsRotations[pointIndex]); } for (int modeIndex = 1; modeIndex < targetSpline.Modes.Length; modeIndex++) { mergedModes.Add(targetSpline.Modes[modeIndex]); mergedNormals.Add(targetSpline.ControlPointsNormals[modeIndex]); } _controlPointsPositions = mergedPoints.ToArray(); _controlPointsRotations = mergedRotations.ToArray(); _modes = mergedModes.ToArray(); _controlPointsNormals = mergedNormals.ToArray(); //Expand vertices limit to avoid autospliting merged spline automatically SplineMeshRenderer splineMeshRenderer = GetComponent(); if (splineMeshRenderer != null) { SplineMeshRenderer targetMeshRenderer = targetSpline.GetComponent(); if (targetMeshRenderer != null) { splineMeshRenderer.VerticesLimit += targetMeshRenderer.VerticesLimit; splineMeshRenderer.EndCap = targetMeshRenderer.EndCap; } } if (deleteTarget) { if (Application.IsPlaying(this)) Destroy(targetSpline.gameObject); //Play mode or build else DestroyImmediate(targetSpline.gameObject); // Unity Editor } else if (disableTargetIfNotDeleted) targetSpline.gameObject.SetActive(false); EnforceHandleAlignment(enforceAligmentIndex); UpdateMeshRenderer(true); } /// /// Connect target to the end of this spline /// /// public void ConnectTarget(GameObject target) { Vector3 point = GetPoint(1f); Vector3 direction = GetDirection(1f); target.transform.position = point; target.transform.LookAt(point + direction); ValidateMeshRendererCaps(this.GetComponent(), target.GetComponent()); } /// /// Creates a new curve to close the gap between this and target object /// /// public void BridgeGap(GameObject target) { _transform = _transform == null ? GetComponent() : _transform; int lastCurveStartIndex = _controlPointsPositions.Length - 4; Vector3 handle1_Direction = GetCurveEndDirection(lastCurveStartIndex); Vector3 handle2_Direction = _transform.InverseTransformDirection(-target.transform.forward);//(-targetSpline.GetCurveStartDirection(0)); AddCurve(); int endPointIndex = _controlPointsPositions.Length - 1; int handle2_Index = _controlPointsPositions.Length - 2; int handle1_Index = _controlPointsPositions.Length - 3; lastCurveStartIndex = _controlPointsPositions.Length - 4; //Recalculate after adding new curve _controlPointsPositions[endPointIndex] = _transform.InverseTransformPoint(target.transform.position); float handleDistance = 0.33f * Vector3.Distance(_controlPointsPositions[endPointIndex], _controlPointsPositions[lastCurveStartIndex]); _controlPointsPositions[handle1_Index] = _controlPointsPositions[lastCurveStartIndex] + handle1_Direction * handleDistance; _controlPointsPositions[handle2_Index] = _controlPointsPositions[endPointIndex] + handle2_Direction * handleDistance; ValidateMeshRendererCaps(this.GetComponent(), target.GetComponent()); } /// /// Insert a new curve between the current selected curve and the next one /// /// /// new selectedIndex public void SubdivideCurve(int selectedIndex) { //Temp lists for inserting operation List newPositions = new List(_controlPointsPositions); List newRotations = new List(_controlPointsRotations); List newModes = new List(_modes); List newNormals = new List(_controlPointsNormals); // Curve segment information int insertIndex = selectedIndex + 2; int insertModeIndex = (selectedIndex / 3) + 1; Vector3 startPosition = _controlPointsPositions[selectedIndex]; Vector3 handle1Position = _controlPointsPositions[selectedIndex + 1]; Vector3 handle2Position = _controlPointsPositions[selectedIndex + 2]; Vector3 endPosition = _controlPointsPositions[selectedIndex + 3]; Quaternion startRotation = _controlPointsRotations[selectedIndex]; Quaternion endRotation = _controlPointsRotations[selectedIndex + 3]; Vector3 newHandlesDirection = (endPosition - startPosition).normalized; Vector3 handle1Direction = (handle1Position - startPosition).normalized; Vector3 handle2Direction = (handle2Position - endPosition).normalized; float handlesDistance = (Vector3.Distance(startPosition, handle1Position) + Vector3.Distance(handle1Position, handle2Position) + Vector3.Distance(handle2Position, endPosition)) / 7f; // Creating curve and handles positions Vector3 newPosition = GetPoint(startPosition, handle1Position, handle2Position, endPosition, 0.5f); Vector3 previousHandlePosition = newPosition + (-newHandlesDirection * handlesDistance); Vector3 afterHandlePosition = newPosition + (newHandlesDirection * handlesDistance); // Recalculate old handles positions handle1Position = startPosition + (handle1Direction * handlesDistance); handle2Position = endPosition + (handle2Direction * handlesDistance); // Curve and handle rotations Quaternion newRotation = Quaternion.Lerp(startRotation, endRotation, 0.5f); Quaternion previousHandleRotation = Quaternion.Lerp(startRotation, newRotation, 0.66f); Quaternion afterHandleRotation = Quaternion.Lerp(newRotation, endRotation, 0.33f); // Curve handle Mode BezierHandlesAlignment newMode = _modes[selectedIndex / 3]; Vector3 newNormal = _controlPointsNormals[selectedIndex / 3]; // Inserting positions newPositions.Insert(insertIndex, afterHandlePosition); newPositions.Insert(insertIndex, newPosition); newPositions.Insert(insertIndex, previousHandlePosition); // Inserting rotations newRotations.Insert(insertIndex, afterHandleRotation); newRotations.Insert(insertIndex, newRotation); newRotations.Insert(insertIndex, previousHandleRotation); // Insert mode newModes.Insert(insertModeIndex, newMode); //Insert normal newNormals.Insert(insertModeIndex, newNormal); //Applying New Control Point Insertion _controlPointsPositions = newPositions.ToArray(); _controlPointsRotations = newRotations.ToArray(); _modes = newModes.ToArray(); _controlPointsNormals = newNormals.ToArray(); //Updates old handles positions _controlPointsPositions[selectedIndex + 1] = handle1Position; _controlPointsPositions[selectedIndex + 5] = handle2Position; Debug.Log("Curve Subdivide Successfully!"); } /// /// Dissolve selected curve /// /// public void DissolveCurve(int selectedIndex) { int previousPoint = selectedIndex - 3; int previousPointHandle1 = selectedIndex - 2; int previousPointHandle2 = selectedIndex - 1; int nextPointHandle1 = selectedIndex + 1; int nextPointHandle2 = selectedIndex + 2; int nextPoint = selectedIndex + 3; // Recalculate handles positions float handlesDistance = Vector3.Distance(_controlPointsPositions[previousPoint], _controlPointsPositions[previousPointHandle1]); handlesDistance += Vector3.Distance(_controlPointsPositions[previousPointHandle1], _controlPointsPositions[previousPointHandle2]); handlesDistance += Vector3.Distance(_controlPointsPositions[previousPointHandle2], _controlPointsPositions[selectedIndex]); handlesDistance += Vector3.Distance(_controlPointsPositions[selectedIndex], _controlPointsPositions[nextPointHandle1]); handlesDistance += Vector3.Distance(_controlPointsPositions[nextPointHandle1], _controlPointsPositions[nextPointHandle2]); handlesDistance += Vector3.Distance(_controlPointsPositions[nextPointHandle2], _controlPointsPositions[nextPoint]); handlesDistance /= 3f; float referenceDistance = Vector3.Distance(_controlPointsPositions[previousPoint], _controlPointsPositions[nextPoint]); handlesDistance = handlesDistance >= referenceDistance * 0.5 ? referenceDistance * 0.33f : handlesDistance; // Make sure handles dont cross os overlap // Calculate handles directions Vector3 handle1Direction = (_controlPointsPositions[previousPointHandle1] - _controlPointsPositions[previousPoint]).normalized; Vector3 handle2Direction = (_controlPointsPositions[nextPointHandle2] - _controlPointsPositions[nextPoint]).normalized; // Update handles positions _controlPointsPositions[previousPointHandle1] = _controlPointsPositions[previousPoint] + (handlesDistance * handle1Direction); _controlPointsPositions[nextPointHandle2] = _controlPointsPositions[nextPoint] + (handlesDistance * handle2Direction); int removeStartIndex = selectedIndex - 1; //remove previous handle, control point and next handle int modeToRemove = (selectedIndex / 3); //Temp lists for removal List newPositions = new List(_controlPointsPositions); List newRotations = new List(_controlPointsRotations); List newModes = new List(_modes); List newNormals = new List(_controlPointsNormals); //Control Point removal newPositions.RemoveRange(removeStartIndex, 3); newRotations.RemoveRange(removeStartIndex, 3); newModes.RemoveAt(modeToRemove); newNormals.RemoveAt(modeToRemove); //Applying Control Point removal _controlPointsPositions = newPositions.ToArray(); _controlPointsRotations = newRotations.ToArray(); _modes = newModes.ToArray(); _controlPointsNormals = newNormals.ToArray(); Debug.Log("Curve Dissolved Successfully!"); } /// /// Populates the OrientedPoints array with a list of evenly spaced points along the spline. /// /// /// /// public OrientedPoint[] CalculateOrientedPoints(float spacing, bool overrideSplineOrientedPoints = true, float resolution = 10) { Profiler.BeginSample("CalculateOrientedPoints"); #region CALCULATING SEGMENTS SPACING float distanceSinceLastEvenPoint = 0; List tempOrientedPoints = new List(); OrientedPoint start, handle1, handle2, end, interpolationPoint; Vector3 curveStartNormal, curveEndNormal; OrientedPoint previousPoint = GetOrientedPoint(0f); // Get start of spline previousPoint.Position = transform.InverseTransformPoint(previousPoint.Position); //Adding spline start point tempOrientedPoints.Add(previousPoint); int lastCurveIndex = ControlPointCount - 3; for (int curveIndex = 0; curveIndex < lastCurveIndex; curveIndex += 3) { start = new OrientedPoint(_controlPointsPositions[curveIndex], _controlPointsRotations[curveIndex]); handle1 = new OrientedPoint(_controlPointsPositions[curveIndex + 1], _controlPointsRotations[curveIndex + 1]); handle2 = new OrientedPoint(_controlPointsPositions[curveIndex + 2], _controlPointsRotations[curveIndex + 2]); end = new OrientedPoint(_controlPointsPositions[curveIndex + 3], _controlPointsRotations[curveIndex + 3]); curveStartNormal = GetControlPointNormal(curveIndex); curveEndNormal = GetControlPointNormal(curveIndex + 3); float controlNetLength = Vector3.Distance(start.Position, handle1.Position) + Vector3.Distance(handle1.Position, handle2.Position) + Vector3.Distance(handle2.Position, end.Position); float estimatedCurveLength = Vector3.Distance(start.Position, end.Position) + controlNetLength / 2f; float divisions = estimatedCurveLength * resolution; float timeSteps = (1f / divisions); float t = 0; while (t <= 1) { t += timeSteps; interpolationPoint = GetOrientedPoint(start.Position, handle1.Position, handle2.Position, end.Position, start.Rotation, handle1.Rotation, handle2.Rotation, end.Rotation, curveStartNormal, curveEndNormal, t); distanceSinceLastEvenPoint += Vector3.Distance(previousPoint.Position, interpolationPoint.Position); while (distanceSinceLastEvenPoint >= spacing) { float exceededDistance = distanceSinceLastEvenPoint - spacing; OrientedPoint newEvenlySpacedPoint = previousPoint; newEvenlySpacedPoint.Position = interpolationPoint.Position + (previousPoint.Position - interpolationPoint.Position).normalized * exceededDistance; newEvenlySpacedPoint.Rotation = interpolationPoint.Rotation; newEvenlySpacedPoint.Up = interpolationPoint.Up; newEvenlySpacedPoint.Forward = interpolationPoint.Forward; tempOrientedPoints.Add(newEvenlySpacedPoint); distanceSinceLastEvenPoint = exceededDistance; previousPoint = newEvenlySpacedPoint; } previousPoint = interpolationPoint; } } int endPointIndex = _controlPointsPositions.Length - 1; int lastEvenlySpacedPointIndex = tempOrientedPoints.Count - 1; float lastPointAndSplineEndDistance = Mathf.Abs(Vector3.Distance(tempOrientedPoints[lastEvenlySpacedPointIndex].Position, _controlPointsPositions[endPointIndex])); interpolationPoint = GetOrientedPoint(1f); // Get end of spline interpolationPoint.Position = transform.InverseTransformPoint(interpolationPoint.Position); #endregion #region ENSURE 10% ERROR MARGIN ON LAST POINTS DISTANCES // Ensure up to 10% error margin on last point distance if (lastPointAndSplineEndDistance <= (spacing * 0.1f)) { tempOrientedPoints[lastEvenlySpacedPointIndex] = interpolationPoint; //Move last point to close gap } else { tempOrientedPoints.Add(interpolationPoint); //Add new point to close gap //Distribute error margin among last 5 points float adjustedpoints = 5f; float adjustmentRatio = 1f; for (int i = tempOrientedPoints.Count - 6; i <= tempOrientedPoints.Count - 2; i++) { if (i <= 0) { adjustedpoints--; continue; } adjustmentRatio = ((adjustedpoints * spacing) + lastPointAndSplineEndDistance) / (adjustedpoints + 1f); OrientedPoint toAdjust = tempOrientedPoints[i]; toAdjust.Position = tempOrientedPoints[i - 1].Position + (tempOrientedPoints[i].Position - tempOrientedPoints[i - 1].Position).normalized * adjustmentRatio; tempOrientedPoints[i] = toAdjust; } } #endregion #region DISABLING UNWANTED COLLIDERS //Disable colliders MeshCollider[] colliders = GetComponentsInChildren(); bool[] collidersState = DisableColliders(colliders); //Ignored objects SMR_IgnoredObject[] ignoredObjects = GameObject.FindObjectsOfType(); bool[] ignoredObjectsState = DisableIgnoredObjects(ignoredObjects); #endregion #region TERRAIN COLLISION DETECTION RaycastHit hit = new RaycastHit(); //Recalculating positions to world space Vector3 worldSpacePos, newForward, newNormal; Quaternion newRotation; Vector3 lastPointPos = (transform.position - transform.forward); Vector3 segmentDirection; for (int i = 0; i < tempOrientedPoints.Count; i++) { worldSpacePos = transform.TransformPoint(tempOrientedPoints[i].Position); newRotation = tempOrientedPoints[i].Rotation; newForward = tempOrientedPoints[i].Forward; newNormal = tempOrientedPoints[i].Up; if (_followTerrain) { if (TerrainCollision(worldSpacePos, out hit)) { worldSpacePos = hit.point; if (i == 0) //First point lastPointPos = (worldSpacePos - transform.forward); segmentDirection = (worldSpacePos - lastPointPos).normalized; newRotation.SetLookRotation(segmentDirection, Vector3.up); newNormal = newRotation * Vector3.up; newForward = segmentDirection; } } tempOrientedPoints[i] = new OrientedPoint(worldSpacePos, newRotation, newForward, newNormal); lastPointPos = tempOrientedPoints[i].Position; } #endregion #region RENABLING UNWANTED COLLIDERS //Renable colliders RenableColliders(colliders, collidersState); //Renable ignored objects RenableIgnoredObjects(ignoredObjects, ignoredObjectsState); #endregion Profiler.EndSample(); if (overrideSplineOrientedPoints) { _orientedPoints = tempOrientedPoints.ToArray(); tempOrientedPoints.Clear(); return _orientedPoints; } else return tempOrientedPoints.ToArray(); } /// /// Reset spline oriented points /// public void DeleteOrientedPoints() { _orientedPoints = null; } /// /// Follow terrain feature collision detection /// /// /// /// True if raycast hits anything [ExecuteInEditMode] private bool TerrainCollision(Vector3 origin, out RaycastHit hit) { Profiler.BeginSample("Follow Terrain Collision"); if (Physics.Raycast((origin + (Vector3.up * _terrainCheckDistance)), Vector3.down, out hit, _terrainCheckDistance * 2)) return true; Profiler.EndSample(); return false; } /// /// Create new spline at the end of the current one /// public GameObject AppendSpline() { ValidateNormals(); Vector3 lastPointPosition = this.GetControlPointPosition(this.ControlPointCount - 1); Quaternion lastPointRotation = this.GetRotation(1f); Vector3 lastPointNormal = this.GetNormal(1f); Vector3 position = transform.TransformPoint(lastPointPosition); GameObject clone = Instantiate(this.gameObject, position, Quaternion.LookRotation(this.GetDirection(1))); Spline newRendererSpline = clone.GetComponent(); newRendererSpline.Reset(); newRendererSpline.ResetNormals(lastPointNormal); newRendererSpline.ResetRotations(lastPointRotation); SplineMeshRenderer newRendererSplineMeshRenderer = clone.GetComponent(); if (newRendererSplineMeshRenderer != null) { SplineMeshRenderer splineMeshRenderer = this.GetComponent(); newRendererSplineMeshRenderer.MeshGenerationMethod = splineMeshRenderer.MeshGenerationMethod; newRendererSplineMeshRenderer.ExtrudeMesh(); ValidateMeshRendererCaps(splineMeshRenderer, newRendererSplineMeshRenderer); } EnforceUniqueName(clone); return clone; } /// /// Create Parallel Spline /// /// public GameObject CreateParallelSpline() { ValidateNormals(); if (_parallelSplineDirection == Vector2.zero) { Debug.LogWarning("Parallel Spline Direction cannot be zero"); return null; } _transform = _transform == null ? GetComponent() : _transform; // Create clone GameObject clone = Instantiate(this.gameObject, this.transform.parent); Transform cloneTransform = clone.GetComponent(); Spline cloneSpline = clone.GetComponent(); // Save points positions Vector3[] worldPositions = new Vector3[_controlPointsPositions.Length]; for (int i = 0; i < worldPositions.Length; i++) { worldPositions[i] = cloneTransform.TransformPoint(_controlPointsPositions[i]); } Vector3[] pointForwards = new Vector3[_controlPointsPositions.Length]; for (int i = 0; i < _controlPointsPositions.Length; i += 3) { //Control point direction if (i < _controlPointsPositions.Length - 1) pointForwards[i] = GetDirection(_controlPointsPositions[i], _controlPointsPositions[i + 1], _controlPointsPositions[i + 2], _controlPointsPositions[i + 3], 0f); else //Last control point pointForwards[i] = GetDirection(_controlPointsPositions[i - 3], _controlPointsPositions[i - 2], _controlPointsPositions[i - 1], _controlPointsPositions[i], 1f); //Handles direction (in relation to control point) if (i < worldPositions.Length - 1) pointForwards[i + 1] = (worldPositions[i + 1] - worldPositions[i]).normalized; //Next handle direction if (i > 0) pointForwards[i - 1] = (worldPositions[i] - worldPositions[i - 1]).normalized; //Previous handle direction } // Update clone object position Vector3 cloneLocalPos = cloneTransform.localPosition; cloneLocalPos += _parallelSplineDirection.x * _transform.right; cloneLocalPos += _parallelSplineDirection.y * _transform.up; cloneTransform.localPosition = cloneLocalPos; // Update control points positions float previousHandleDistance = 0f, nextHandleDistance = 0f; Vector3 newPoint, previousHandle, nextHandle, pointNormal, pointRight; for (int i = 0; i < worldPositions.Length; i += 3) { pointNormal = GetControlPointNormal(i); pointRight = Vector3.Cross(pointNormal, pointForwards[i]).normalized; newPoint = worldPositions[i]; newPoint += pointRight * _parallelSplineDirection.x; newPoint += _transform.up * _parallelSplineDirection.y; cloneSpline.ControlPointsPositions[i] = cloneTransform.InverseTransformPoint(newPoint); } // Update handles positions float newCurveRatio = 1f, originalDistance, newDistance, previousCurveRatio = 1f; for (int i = 0; i < worldPositions.Length; i += 3) { if (i < worldPositions.Length - 1) { originalDistance = Vector3.Distance(_controlPointsPositions[i], _controlPointsPositions[i + 3]); newDistance = Vector3.Distance(cloneSpline.ControlPointsPositions[i], cloneSpline.ControlPointsPositions[i + 3]); newCurveRatio = newDistance / originalDistance; } newPoint = cloneTransform.TransformPoint(cloneSpline.ControlPointsPositions[i]); if (i > 0) previousHandleDistance = Vector3.Distance(worldPositions[i], worldPositions[i - 1]); if (i < worldPositions.Length - 1) nextHandleDistance = Vector3.Distance(worldPositions[i], worldPositions[i + 1]); if (i > 0) { previousHandle = newPoint - pointForwards[i - 1] * previousHandleDistance * previousCurveRatio; cloneSpline.ControlPointsPositions[i - 1] = cloneTransform.InverseTransformPoint(previousHandle); } if (i < worldPositions.Length - 1) { nextHandle = newPoint + pointForwards[i + 1] * nextHandleDistance * newCurveRatio; cloneSpline.ControlPointsPositions[i + 1] = cloneTransform.InverseTransformPoint(nextHandle); } previousCurveRatio = newCurveRatio; } // Update mesh renderer (if any) cloneSpline.UpdateMeshRenderer(true); EnforceUniqueName(clone); return clone; } /// /// Revert spline direction /// public void RevertSpline() { _transform = _transform == null ? GetComponent() : _transform; //Instantiate temporary data sets List revertedPositions = new List(); List revertedRotations = new List(); List revertedModes = new List(); List revertedNormals = new List(); //Get target location and look direction Vector3 targetPosition = _transform.TransformPoint(_controlPointsPositions[_controlPointsPositions.Length - 1]); Vector3 targetDirection = _transform.TransformPoint(_controlPointsPositions[_controlPointsPositions.Length - 2]); //Save reverted positions for (int i = _controlPointsPositions.Length - 1; i >= 0; i--) { revertedPositions.Add(_transform.TransformPoint(_controlPointsPositions[i])); } //Save reverted rotations for (int i = _controlPointsRotations.Length - 1; i >= 0; i--) { revertedRotations.Add(_controlPointsRotations[i]); } //Save reverted modes for (int i = _modes.Length - 1; i >= 0; i--) { revertedModes.Add(_modes[i]); } //Save reverted normals for (int i = _controlPointsNormals.Length - 1; i >= 0; i--) { revertedNormals.Add(_controlPointsNormals[i]); } //Move spline to new position _transform.position = targetPosition; _transform.LookAt(targetDirection); //Apply reverted positions for (int i = 0; i < revertedPositions.Count; i++) { _controlPointsPositions[i] = _transform.InverseTransformPoint(revertedPositions[i]); } //Apply reverted rotations for (int i = 0; i < revertedRotations.Count; i++) { _controlPointsRotations[i] = revertedRotations[i]; } //Apply reverted modes for (int i = 0; i < revertedModes.Count; i++) { _modes[i] = revertedModes[i]; } //Apply reverted normals for (int i = 0; i < revertedNormals.Count; i++) { _controlPointsNormals[i] = revertedNormals[i]; } UpdateMeshRenderer(true); } /// /// Update mesh renderer caps for seamless connection /// /// /// private static void ValidateMeshRendererCaps(SplineMeshRenderer previous, SplineMeshRenderer next) { if (previous == null || next == null) return; next.StartCap = false; // Disable start cap for seamless connection previous.EndCap = false; // Disable end cap for seamless connection next.SpawnCaps(); previous.SpawnCaps(); } /// /// Check if spline has changed /// /// public bool CheckChanges() { bool hasChanged = false; if (_transform == null) _transform = GetComponent(); hasChanged = (_lastTransformPosition != _transform.position) || (_lastTransformRotation != _transform.rotation); _lastTransformPosition = _transform.position; _lastTransformRotation = _transform.rotation; return hasChanged; } /// /// Adjuste terrains to spline /// public void Terraform(bool setHeights = true, bool paintTextures = true) { try { OrientedPoint[] orientedPoints = CalculateOrientedPoints(1f, true); _transform = _transform == null ? GetComponent() : _transform; List terrains = new List(); int[] terrainIndexes = new int[orientedPoints.Length]; Vector3[] path = new Vector3[orientedPoints.Length]; Vector3[] directions = new Vector3[orientedPoints.Length]; RaycastHit hit; Vector3 worldPos; Vector3 lastPointPos = Vector3.zero; Vector3 forward; Vector3 right; #region DISABLING UNWANTED COLLIDERS //Disable colliders MeshCollider[] colliders = GetComponentsInChildren(); bool[] collidersState = DisableColliders(colliders); //Ignored objects SMR_IgnoredObject[] ignoredObjects = GameObject.FindObjectsOfType(); bool[] ignoredObjectsState = DisableIgnoredObjects(ignoredObjects); #endregion _terraformingStepsCount = orientedPoints.Length; for (int i = 0; i < orientedPoints.Length; i++) { _terraformingCurrentStep = i; DisplayProgressBar("Scanning Terrains..."); worldPos = orientedPoints[i].Position; //Oriented points is already in world position if (i == 0) forward = (worldPos - (worldPos - transform.forward)).normalized; else forward = (worldPos - lastPointPos).normalized; right = Vector3.Cross(Vector3.up, forward).normalized; lastPointPos = worldPos; if (TerrainCollision(worldPos, out hit)) { Terrain terrain = hit.collider.GetComponent(); if (terrain != null) { if (!terrains.Contains(terrain)) terrains.Add(terrain); terrainIndexes[i] = terrains.IndexOf(terrain); terrain.allowAutoConnect = true; } } else { terrainIndexes[i] = -1; } path[i] = worldPos; directions[i] = right; } Terrain.SetConnectivityDirty(); //tries to reconnect terrains #region RENABLING UNWANTED COLLIDERS //Renable colliders RenableColliders(colliders, collidersState); //Renable ignored objects RenableIgnoredObjects(ignoredObjects, ignoredObjectsState); #endregion List terrainPath = new List(); List slopeDirection = new List(); _terraformingStepsCount = terrains.Count * 3; //3 operations (Set height, smooth height, paint) _terraformingCurrentStep = 0f; //Identify all terrains and respective points for (int i = 0; i < terrains.Count; i++) { terrainPath.Clear(); for (int j = 0; j < terrainIndexes.Length; j++) { if (terrainIndexes[j] == i) { terrainPath.Add(path[j]); slopeDirection.Add(directions[j]); } } if (setHeights) { List pathCoordinates; DisplayProgressBar("Terraforming..."); WSM_TerrainTools.AdjustHeightAlongPath(terrains[i], terrainPath.ToArray(), _terraformingWidth, out pathCoordinates); _terraformingCurrentStep++; DisplayProgressBar("Smoothing slopes..."); WSM_TerrainTools.SmoothenSlopeAlongPath(terrains[i], terrainPath.ToArray(), _terraformingWidth, _embankmentWidth, _embankmentSlope, pathCoordinates); } if (paintTextures) { _terraformingCurrentStep++; DisplayProgressBar("Painting terrains..."); WSM_TerrainTools.PaintTextureAlongPath(terrains[i], terrainPath.ToArray(), _terraformingTexture, _terraformingWidth, _terraformingTexture, _embankmentWidth, _minTextureBlending, _maxTextureBlending, _minTextureBlending, _maxTextureBlending); } _terraformingCurrentStep++; DisplayProgressBar("Finishing..."); WSM_TerrainTools.StitchBorders(terrains[i]); } } catch (Exception ex) { Debug.LogError(ex); } finally { CloseProgressBar(); } } private void DisplayProgressBar(string info) { #if UNITY_EDITOR UnityEditor.EditorUtility.DisplayProgressBar("Terraforming", info, TerraformingProgress); #endif } private static void CloseProgressBar() { #if UNITY_EDITOR UnityEditor.EditorUtility.ClearProgressBar(); #endif } /// /// Create or update terrain backups /// public void BackupTerrains() { Terrain[] terrains = FindObjectsOfType(); if (terrains == null) return; foreach (Terrain terrain in terrains) { WSM_TerrainTools.Backup(terrain); } } /// /// Restore terrain bakcups (if any) /// WARNING: Affects all terrains in the scene /// public void RestoreTerrains() { Terrain[] terrains = FindObjectsOfType(); if (terrains == null) return; foreach (Terrain terrain in terrains) { WSM_TerrainTools.RestoreBackup(terrain); } } /// /// Close or open spline based on loop property current state /// private void EnforceLoop() { if (_loop == true) { _modes[_modes.Length - 1] = _modes[0]; Vector3 handleDirection = transform.InverseTransformDirection(-GetDirection(0)); float handleDistance = Vector3.Distance(_controlPointsPositions[_controlPointsPositions.Length - 1], _controlPointsPositions[_controlPointsPositions.Length - 2]); _controlPointsPositions[_controlPointsPositions.Length - 1] = _controlPointsPositions[0]; _controlPointsPositions[_controlPointsPositions.Length - 2] = _controlPointsPositions[_controlPointsPositions.Length - 1] + handleDirection * handleDistance; } else { ResetLastCurve(); } } /// /// Project all nodes (control points) on surface /// public void ProjectNodesOnSurface() { for (int i = 0; i < _controlPointsPositions.Length; i += 3) { ProjectNodeOnSurface(i); } } /// /// Project target node (control point) on surface /// /// public void ProjectNodeOnSurface(int index) { _transform = _transform == null ? GetComponent() : _transform; #region DISABLING UNWANTED COLLIDERS //Disable colliders MeshCollider[] colliders = GetComponentsInChildren(); bool[] collidersState = DisableColliders(colliders); //Ignored objects SMR_IgnoredObject[] ignoredObjects = GameObject.FindObjectsOfType(); bool[] ignoredObjectsState = DisableIgnoredObjects(ignoredObjects); #endregion #region TERRAIN COLLISION DETECTION RaycastHit hit = new RaycastHit(); //Recalculating positions to world space Vector3 worldSpacePos; worldSpacePos = _transform.TransformPoint(_controlPointsPositions[index]); if (TerrainCollision(worldSpacePos, out hit)) { worldSpacePos = hit.point; if (index == 0) //First point { _transform.position = worldSpacePos; SetControlPointPosition(index, Vector3.zero); } else { SetControlPointPosition(index, _transform.InverseTransformPoint(worldSpacePos)); } } #endregion #region RENABLING UNWANTED COLLIDERS //Renable colliders RenableColliders(colliders, collidersState); //Renable ignored objects RenableIgnoredObjects(ignoredObjects, ignoredObjectsState); #endregion } } }