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
}
}
}