You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

994 lines
44 KiB
C#

3 years ago
using System;
using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Pegasus
{
/// <summary>
/// Add this object to a a game object in the scene to include it in a flythrough
/// </summary>
///
public class PegasusPoi : MonoBehaviour
{
[Tooltip("The type of POI. Auto generated POI's are subject to automatic deletion. Manual POI's will always be kept.")]
public PegasusConstants.PoiType m_poiType = PegasusConstants.PoiType.Manual;
[Tooltip("The mechanism used to check height for the POI.")]
public PegasusConstants.PoiHeightCheckType m_heightCheckType = PegasusConstants.PoiHeightCheckType.ManagerSettings;
[Tooltip("The lookat type for this POI segement. Changing this will re-generate the lookat location.")]
public PegasusConstants.LookatType m_lookatType = PegasusConstants.LookatType.Path;
[Tooltip("The lookat angle from the POI.")]
public float m_lookAtAngle = 0f;
[Tooltip("The lookat distance from the POI.")]
public float m_lookAtDistance = 0f;
[Tooltip("The lookat height above the ground from the POI.")]
public float m_lookAtHeight = 0f;
[Tooltip("The actual lookat location for the POI segment.")]
public Vector3 m_lookatLocation = Vector3.zero;
[Tooltip("The start speed type for this segment. The segement will always start at this speed and ease to the next segments start speed.")]
public PegasusConstants.SpeedType m_startSpeedType = PegasusConstants.SpeedType.Medium;
[Tooltip("The actual start speed for this segment. Speed varies between stat speed for this segment and start speed for next segment.")]
public float m_startSpeed = 1f;
[HideInInspector]
public PegasusConstants.EasingType m_rotationEasingType = PegasusConstants.EasingType.Linear;
[HideInInspector]
public PegasusConstants.EasingType m_velocityEasingType = PegasusConstants.EasingType.EaseInOut;
[HideInInspector]
public PegasusConstants.EasingType m_positionEasingType = PegasusConstants.EasingType.Linear;
[HideInInspector]
public PegasusManager m_manager; //The manager which created this poi and owns it
[HideInInspector]
public bool m_alwaysShowGizmos = true; //Whether we should show gizmos even when not selected
[HideInInspector]
public float m_segmentDistance = 0f; //The actual travel distance of this segment based on spline
[HideInInspector]
public TimeSpan m_segmentStartTime = TimeSpan.Zero; //The time after start time that this segment will start playing
[HideInInspector]
public TimeSpan m_segmentDuration = TimeSpan.Zero; //The amount of time it will take to traverse this segment
[HideInInspector]
public int m_segmentIndex = 0; //The index of this segment in the overal flythrough
[HideInInspector]
public bool m_isFirstPOI = true; //Whether or not we are the first POI
[HideInInspector]
public bool m_isLastPOI = true; //Whether or not we are the last POI
[HideInInspector]
public PegasusPoi m_prevPoi; //The previous POI, will initially be set this
[HideInInspector]
public PegasusPoi m_nextPoi; //The next POI, will initially be set to this
[HideInInspector]
public List<Vector3> m_poiSteps = new List<Vector3>(); //The steps that this POI segment will traverse through
[HideInInspector]
public bool m_isSelected = false; //Whether or not this poi is selected - draws a different line
[HideInInspector]
public List<TriggerBase> m_poiTriggers = new List<TriggerBase>(); //The triggers associated with this poi
[HideInInspector]
public bool m_autoRollOn = false; //Whether or not this POI should calculate banking automatically
[HideInInspector]
public Vector3 m_lastRotation = Vector3.zero; //Last rotation euler angles - used to disable auto roll
#region Unity gizmos n editing
/// <summary>
/// Draw gizmos when selected
/// </summary>
private void OnDrawGizmosSelected()
{
DrawGizmos(true);
}
/// <summary>
/// Draw gizmos when not selected
/// </summary>
private void OnDrawGizmos()
{
DrawGizmos(false);
}
/// <summary>
/// Draw the gizmos
/// </summary>
/// <param name="isSelected"></param>
private void DrawGizmos(bool isSelected)
{
//Determine whether to drop out
if (!isSelected && !m_alwaysShowGizmos)
{
return;
}
//Check to see if we have a change in the state of our list - force an initialise if so
if (transform.parent.childCount != m_manager.m_poiList.Count)
{
m_manager.InitialiseFlythrough();
}
#if UNITY_EDITOR
//Make a check to see if we are selected - disable if not
if (Selection.activeGameObject != null && Selection.activeGameObject.GetInstanceID() != this.gameObject.GetInstanceID())
{
m_isSelected = false;
}
#endif
//Now draw the gizmos
//Segment path spline
float velRange = PegasusConstants.SpeedStratospheric - PegasusConstants.SpeedReallySlow;
if (m_nextPoi != null)
{
//See if we need to draw the segments for this
bool drawSegments = true;
if (m_isLastPOI && m_manager.m_flythroughType == PegasusConstants.FlythroughType.SingleShot)
{
drawSegments = false;
}
if (drawSegments)
{
float inc = 0.05f;
float vel1, vel2;
Vector3 pos1, pos2;
pos1 = CalculatePositionSpline(0f);
vel1 = CalculateVelocity(0f);
for (float pct = inc; pct <= 1.02f; pct += inc)
{
//pos2 = CalculatePositionSpline(pct);
pos2 = CalculatePositionLinear(pct);
vel2 = CalculateVelocity(pct);
if (m_isSelected)
{
Gizmos.color = Color.magenta * Color.Lerp(Color.cyan, Color.red, ((vel1 + vel2 / 2f) - PegasusConstants.SpeedReallySlow) / velRange);
}
else
{
Gizmos.color = Color.Lerp(Color.cyan, Color.red, ((vel1 + vel2 / 2f) - PegasusConstants.SpeedReallySlow) / velRange);
}
Gizmos.DrawLine(pos1, pos2);
vel1 = vel2;
pos1 = pos2;
}
}
}
/*
Vector3 point1 = m_poiList[segment + 1].m_targetLocation;
Vector3 point2 = m_poiList[segment + 2].m_targetLocation;
Vector3 tangent2 = m_poiList[segment + 3].m_targetLocation;
Vector3 t2 = Vector3.Normalize(m_poiList[segment + 2].m_targetLocation - m_poiList[segment + 3].m_targetLocation) * smooth;
Gizmos.color = Color.magenta;
Gizmos.DrawLine(m_poiList[segment + 2].m_targetLocation, m_poiList[segment + 2].m_targetLocation + t2);
tangent2 = m_poiList[segment + 2].m_targetLocation + t2;
Gizmos.color = Color.green;
for (int i = 0; i < 30; i++)
{
Gizmos.DrawLine(Hermite(point1, tangent1, point2, tangent2, (float) i/40f),
Hermite(point1, tangent1, point2, tangent2, (float)(i + 1)/40f));
}
*/
//Only draw this in correct scenario
if (m_lookatType == PegasusConstants.LookatType.Target)
{
//Line to lookat location
Gizmos.color = Color.Lerp(Color.cyan, Color.red, (m_startSpeed - PegasusConstants.SpeedReallySlow) / velRange);
Gizmos.DrawLine(transform.position, m_lookatLocation);
//Lookat sphere
Gizmos.color = Color.cyan;
Gizmos.DrawSphere(m_lookatLocation, 0.25f);
}
//Are we last one, and is play next chosen
Gizmos.color = Color.yellow;
if (m_isLastPOI == true && m_manager.m_flythroughType == PegasusConstants.FlythroughType.SingleShot &&
m_manager.m_flythroughEndAction == PegasusConstants.FlythroughEndAction.PlayNextPegasus &&
m_manager.m_nextPegasus != null)
{
PegasusPoi nextPoi = m_manager.m_nextPegasus.GetFirstPOI();
if (nextPoi != null)
{
Gizmos.DrawLine(transform.position, nextPoi.transform.position);
}
}
//Flythrough location
Gizmos.DrawSphere(transform.position, m_manager.m_poiGizmoSize);
}
#endregion
/// <summary>
/// Return true if the other object is the same as this one
/// </summary>
/// <param name="poi">The other object to check</param>
/// <returns></returns>
public bool IsSameObject(PegasusPoi poi)
{
if (poi == null)
{
return false;
}
if (this.GetInstanceID() == poi.GetInstanceID())
{
return true;
}
return false;
}
/// <summary>
/// Initialise this POI's settings, leveraging the manager
/// </summary>
/// <param name="updateSegments">Update the segments</param>
public void Initialise(bool updateSegments)
{
//Make sure prev and next set up - if not then exit
if (m_prevPoi == null || m_nextPoi == null)
{
return;
}
//Move us if we are below the minimum height on the terrain
transform.position = m_manager.GetValidatedPoiPosition(transform.position, m_heightCheckType);
//Update last rotation
m_lastRotation = transform.rotation.eulerAngles;
//Set up easing calculators
switch (m_velocityEasingType)
{
case PegasusConstants.EasingType.Linear:
m_velocityEasingCalculator = new Easing(EaseLinear);
break;
case PegasusConstants.EasingType.EaseIn:
m_velocityEasingCalculator = new Easing(EaseIn);
break;
case PegasusConstants.EasingType.EaseOut:
m_velocityEasingCalculator = new Easing(EaseOut);
break;
case PegasusConstants.EasingType.EaseInOut:
m_velocityEasingCalculator = new Easing(EaseInOut);
break;
}
switch (m_rotationEasingType)
{
case PegasusConstants.EasingType.Linear:
m_rotationEasingCalculator = new Easing(EaseLinear);
break;
case PegasusConstants.EasingType.EaseIn:
m_rotationEasingCalculator = new Easing(EaseIn);
break;
case PegasusConstants.EasingType.EaseOut:
m_rotationEasingCalculator = new Easing(EaseOut);
break;
case PegasusConstants.EasingType.EaseInOut:
m_rotationEasingCalculator = new Easing(EaseInOut);
break;
}
switch (m_positionEasingType)
{
case PegasusConstants.EasingType.Linear:
m_positionEasingCalculator = new Easing(EaseLinear);
break;
case PegasusConstants.EasingType.EaseIn:
m_positionEasingCalculator = new Easing(EaseIn);
break;
case PegasusConstants.EasingType.EaseOut:
m_positionEasingCalculator = new Easing(EaseOut);
break;
case PegasusConstants.EasingType.EaseInOut:
m_positionEasingCalculator = new Easing(EaseInOut);
break;
}
//Now move things around and update variables based on lookat type
switch (m_lookatType)
{
case PegasusConstants.LookatType.Path:
m_lookatLocation = CalculatePositionSpline(0.005f);
GetRelativeOffsets(transform.position, m_lookatLocation, out m_lookAtDistance, out m_lookAtHeight, out m_lookAtAngle);
break;
case PegasusConstants.LookatType.Target:
GetRelativeOffsets(transform.position, m_lookatLocation, out m_lookAtDistance, out m_lookAtHeight, out m_lookAtAngle);
break;
}
//Setup the start and end rotations - required for progression calculations - note this assumes that the next poi lookat location has been calculated already
//Handle auto roll - overrides the z rotation
if (m_autoRollOn)
{
if (m_manager.m_flythroughType == PegasusConstants.FlythroughType.Looped || (!m_isFirstPOI && !m_isLastPOI))
{
Vector3 posPrev = m_prevPoi.transform.position;
Vector3 posOrigin = transform.position;
Vector3 posTarget = m_nextPoi.transform.position;
//float distOriginTarget = Vector3.Distance(posOrigin, posTarget);
//Create forward location and heading based on prev location to current location -> project it forward
Vector3 hdgPrevOrigin = posOrigin - posPrev;
// hdgPrevOrigin.Normalize();
// Vector3 posForward = posOrigin + Quaternion.Euler(transform.localEulerAngles) * (hdgPrevOrigin * distOriginTarget);
Quaternion qOriginForward = Quaternion.identity;
if (hdgPrevOrigin != Vector3.zero)
{
qOriginForward = Quaternion.LookRotation(hdgPrevOrigin);
}
//Origin to target
Vector3 hdgOriginTarget = posTarget - posOrigin;
Quaternion qOriginTarget = Quaternion.identity;
if (hdgOriginTarget != Vector3.zero)
{
qOriginTarget = Quaternion.LookRotation(hdgOriginTarget);
}
//Scale things
float angle = qOriginTarget.eulerAngles.y - qOriginForward.eulerAngles.y;
float scaledSpeed = Mathf.Clamp(m_startSpeed, PegasusConstants.SpeedReallySlow, m_manager.m_autoRollMaxSpeed) / m_manager.m_autoRollMaxSpeed;
float scaledAngle = angle;
if (angle < 0f)
{
scaledAngle = Mathf.Clamp(angle, -90f, 0f) / 90f;
}
else
{
scaledAngle = Mathf.Clamp(angle, 0f, 90f) / 90f;
}
float z = scaledAngle * scaledSpeed * m_manager.m_autoRollMaxAngle * -1f;
transform.localRotation = Quaternion.Euler(0f, 0f, z);
m_lastRotation = transform.localEulerAngles;
/*
Vector3 pos1 = new Vector3(m_prevPoi.transform.position.x, 0f, m_prevPoi.transform.position.z);
Vector3 pos2 = new Vector3(transform.position.x, 0f, transform.position.z);
Vector3 pos3 = new Vector3(m_nextPoi.transform.position.x, 0f, m_nextPoi.transform.position.z);
Vector3 hdg12 = pos2 - pos1;
Vector3 hdg13 = pos3 - pos1;
Quaternion q1 = Quaternion.identity;
if (hdg12 != Vector3.zero)
{
q1 = Quaternion.LookRotation(hdg12);
}
Quaternion q2 = Quaternion.identity;
if (hdg13 != Vector3.zero)
{
q2 = Quaternion.LookRotation(hdg13);
}
float angle = q1.eulerAngles.y - q2.eulerAngles.y;
float scaledSpeed = Mathf.Clamp(m_startSpeed, PegasusConstants.SpeedReallySlow, m_manager.m_autoRollMaxSpeed) / m_manager.m_autoRollMaxSpeed;
float scaledAngle = angle;
if (angle < 0f)
{
scaledAngle = Mathf.Clamp(angle, -90f, 0f) / 90f;
}
else
{
scaledAngle = Mathf.Clamp(angle, 0f, 90f) / 90f;
}
float z = scaledAngle*scaledSpeed*m_manager.m_autoRollMaxAngle;
transform.localRotation = Quaternion.Euler(0f, 0f, z);
m_lastRotation = transform.localEulerAngles;
*/
}
}
//Then set up the rest of the rotations
Vector3 rotationDir = m_lookatLocation - transform.position;
if (rotationDir != Vector3.zero)
{
m_rotationStart = Quaternion.LookRotation(m_lookatLocation - transform.position) * transform.localRotation;
}
else
{
m_rotationStart = transform.localRotation;
}
rotationDir = m_nextPoi.m_lookatLocation - m_nextPoi.transform.position;
if (rotationDir != Vector3.zero)
{
m_rotationEnd = Quaternion.LookRotation(rotationDir) * m_nextPoi.transform.localRotation;
}
else
{
m_rotationEnd = m_nextPoi.transform.localRotation;
}
//Speed
switch (m_startSpeedType)
{
case PegasusConstants.SpeedType.ReallySlow:
m_startSpeed = PegasusConstants.SpeedReallySlow;
break;
case PegasusConstants.SpeedType.Slow:
m_startSpeed = PegasusConstants.SpeedSlow;
break;
case PegasusConstants.SpeedType.Medium:
m_startSpeed = PegasusConstants.SpeedMedium;
break;
case PegasusConstants.SpeedType.Fast:
m_startSpeed = PegasusConstants.SpeedFast;
break;
case PegasusConstants.SpeedType.ReallyFast:
m_startSpeed = PegasusConstants.SpeedReallyFast;
break;
case PegasusConstants.SpeedType.Stratospheric:
m_startSpeed = PegasusConstants.SpeedStratospheric;
break;
}
//Update travel distance for the segment
if (updateSegments)
{
UpdateSegment();
}
//Get the POI triggers
m_poiTriggers.Clear();
m_poiTriggers.AddRange(gameObject.GetComponentsInChildren<TriggerBase>());
}
/// <summary>
/// Called when this poi starts its flythrough
/// </summary>
public void OnStartTriggers()
{
for (int idx = 0; idx < m_poiTriggers.Count; idx++)
{
m_poiTriggers[idx].OnStart(this);
}
}
/// <summary>
/// Called when this poi continues its flythrough
/// </summary>
public void OnUpdateTriggers(float progress)
{
for (int idx = 0; idx < m_poiTriggers.Count; idx++)
{
m_poiTriggers[idx].OnUpdate(this, progress);
}
}
/// <summary>
/// Called when this poi starts its flythrough
/// </summary>
public void OnEndTriggers()
{
for (int idx = 0; idx < m_poiTriggers.Count; idx++)
{
m_poiTriggers[idx].OnEnd(this);
}
}
#region Handy value getters
/// <summary>
/// Get the poi start speed based on the speed type
/// </summary>
/// <param name="speedType"></param>
/// <returns></returns>
public float GetStartSpeed(PegasusConstants.SpeedType speedType)
{
switch (speedType)
{
case PegasusConstants.SpeedType.ReallySlow:
return PegasusConstants.SpeedReallySlow;
case PegasusConstants.SpeedType.Slow:
return PegasusConstants.SpeedSlow;
case PegasusConstants.SpeedType.Medium:
return PegasusConstants.SpeedMedium;
case PegasusConstants.SpeedType.Fast:
return PegasusConstants.SpeedFast;
case PegasusConstants.SpeedType.ReallyFast:
return PegasusConstants.SpeedReallyFast;
case PegasusConstants.SpeedType.Stratospheric:
return PegasusConstants.SpeedStratospheric;
}
return m_startSpeed;
}
/// <summary>
/// Update the current segment distance
/// </summary>
public void UpdateSegment()
{
m_segmentDistance = 0f;
m_segmentDuration = TimeSpan.Zero;
m_segmentStartTime = TimeSpan.Zero;
if (!m_isFirstPOI)
{
m_segmentStartTime = m_prevPoi.m_segmentStartTime + m_prevPoi.m_segmentDuration;
}
m_poiSteps.Clear();
if (m_manager.m_flythroughType == PegasusConstants.FlythroughType.SingleShot && m_manager.GetNextPOI(this, false) == null)
{
return;
}
float pct = 0f;
Vector3 pos1 = Vector3.zero;
Vector3 pos2 = Vector3.zero;
if (m_nextPoi != null)
{
//Calculate the segment distance by iterating over the spline - at a resolution related to the overall distance
int stepsPerMeter = 3;
int measurementsPerMeter = stepsPerMeter * 20; //Another magic multiplier - the more steps per meter the more measurements are required
int measurement;
float straightLineDistance = Vector3.Distance(transform.position, m_nextPoi.transform.position);
int totalMeasurments = (int)Mathf.Ceil((float)measurementsPerMeter * straightLineDistance);
float measurementIncrement = 1f / (float)totalMeasurments;
float measurementDistance = 0f;
float steppedDistance = 0f;
float minMeasurementDistance = 0f;
float maxMeasurementDistance = 0f;
float totalSteppedDistance = 0f;
float minMeasuredStepDistance = 0f;
float maxMeasuredStepDistance = 0f;
pos1 = transform.position;
for (measurement = 1, pct = 0f, minMeasurementDistance = 0f, maxMeasurementDistance = 0f; measurement <= totalMeasurments; measurement++)
{
pct += measurementIncrement;
pos2 = CalculatePositionSpline(pct);
pos2 = m_manager.GetValidatedPoiPosition(pos2, m_heightCheckType);
measurementDistance = Vector3.Distance(pos1, pos2);
m_segmentDistance += measurementDistance;
pos1 = pos2;
//For debugging
if (ApproximatelyEqual(minMeasurementDistance, 0f) || (measurementDistance < minMeasurementDistance))
{
minMeasurementDistance = measurementDistance;
}
if (ApproximatelyEqual(maxMeasurementDistance, 0f) || (measurementDistance > maxMeasurementDistance))
{
maxMeasurementDistance = measurementDistance;
}
}
//Debug.Log(string.Format("{0} - meas dist {1:0.0000}, min dist {2:0.0000}, max dist {3:0.0000}, staight length {4:0.000}, spline length {5:0.000}", transform.name, 1f / (float)measurementsPerMeter , minMeasurementDistance, maxMeasurementDistance, straightLineDistance, m_segmentDistance));
//Refine steps - increase them if there is a small distance in oder to create a smoother movement
if (m_segmentDistance < 2f)
{
stepsPerMeter *= 3; //Arbitrar magic value
}
//Now add in the actual steps - the biggest thing they need to be to get consistent speed is as close to eqidistant as possible
float expectedStepDistance = 1f / (float)stepsPerMeter; //We want an exact multiple that gets to the whole length, but is nearest to 1 / stepsPerMeter
expectedStepDistance = m_segmentDistance / Mathf.Floor(m_segmentDistance / expectedStepDistance);
pos1 = transform.position;
m_poiSteps.Add(pos1);
for (measurement = 1, pct = 0f, minMeasuredStepDistance = 0f, maxMeasuredStepDistance = 0f; measurement <= totalMeasurments; measurement++)
{
pct += measurementIncrement;
pos2 = CalculatePositionSpline(pct);
pos2 = m_manager.GetValidatedPoiPosition(pos2, m_heightCheckType);
measurementDistance = Vector3.Distance(pos1, pos2);
steppedDistance += measurementDistance;
if (steppedDistance >= expectedStepDistance)
{
//For debugging
if (ApproximatelyEqual(minMeasuredStepDistance, 0f) || (steppedDistance < minMeasuredStepDistance))
{
minMeasuredStepDistance = steppedDistance;
}
if (ApproximatelyEqual(maxMeasuredStepDistance, 0f) || (steppedDistance > maxMeasuredStepDistance))
{
maxMeasuredStepDistance = steppedDistance;
}
//Lerp the intermediary steps to maintain distances
while (steppedDistance >= expectedStepDistance)
{
m_poiSteps.Add(Vector3.Lerp(m_poiSteps[m_poiSteps.Count-1], pos2, expectedStepDistance / steppedDistance));
steppedDistance -= expectedStepDistance;
totalSteppedDistance += expectedStepDistance;
}
}
pos1 = pos2;
}
//Debug.Log(string.Format("{0} - exp step dist {1:0.0000}, min step dist {2:0.0000}, max step dist {3:0.0000}, straight length {4:0.000}, spline length {5:0.000}, stepped length {6:0.000}, steps {7}, ERROR {8:0.000}, ERROR % {9:0.00}", transform.name, expectedStepDistance, minMeasuredStepDistance, maxMeasuredStepDistance, straightLineDistance, m_segmentDistance, totalSteppedDistance, m_poiSteps.Count, totalSteppedDistance - m_segmentDistance, ((totalSteppedDistance - m_segmentDistance) / expectedStepDistance) * 100f));
//Check the last position, if we just missed out then add another, otherwise move the last one so it is exact
if (((totalSteppedDistance - m_segmentDistance) / expectedStepDistance) < -0.5f)
{
m_poiSteps.Add(m_nextPoi.transform.position);
}
else
{
m_poiSteps[m_poiSteps.Count - 1] = m_nextPoi.transform.position;
}
}
//Update the duration
UpdateSegmentDuration();
}
/// <summary>
/// Update the segments duration - needs to be called after update segment
/// </summary>
public void UpdateSegmentDuration()
{
//Update the expected traversal time (based on either fps setting, or an average of 60fps)
m_segmentDuration = TimeSpan.Zero;
float pct = 0f;
Vector3 pos1 = CalculatePositionLinear(0f);
Vector3 pos2 = Vector3.zero;
float duration = 0f;
for (pct = 0f; pct < 1f; pct += 0.05f)
{
pos2 = CalculatePositionLinear(pct);
duration += Vector3.Distance(pos1, pos2) / CalculateVelocity(pct);
pos1 = pos2;
}
m_segmentDuration = TimeSpan.FromSeconds(duration);
}
#endregion
#region Progression variables and calculations - uses more memory to reduce allocations that can cause framerate jitter
/// <summary>
/// Easing calculator used to smooth calculations - stops the jolts that kill immersion
/// </summary>
/// <param name="time"></param>
/// <param name="duration"></param>
/// <returns></returns>
delegate float Easing(float time, float duration = 1f);
private Easing m_velocityEasingCalculator = new Easing(EaseLinear);
private Easing m_positionEasingCalculator = new Easing(EaseLinear);
private Easing m_rotationEasingCalculator = new Easing(EaseLinear);
/// <summary>
/// Variables used to determine lookat target and rotation
/// </summary>
public Quaternion m_rotationStart = Quaternion.identity;
public Quaternion m_rotationEnd = Quaternion.identity;
/// <summary>
/// Calculate progress on the variables supplied
/// </summary>
/// <param name="percent">The percentage through the segment that the calculations should be made for from 0 == 0% to 1 == 100%</param>
/// <param name="velocity">Velocity at that point</param>
/// <param name="position">Position at that point</param>
/// <param name="rotation">Rotation at that point</param>
public void CalculateProgress(float percent, out float velocity, out Vector3 position, out Quaternion rotation)
{
velocity = CalculateVelocity(percent);
rotation = CalculateRotation(percent);
position = CalculatePositionLinear(percent);
}
/// <summary>
/// Calculate position based on the variables supplied
/// </summary>
/// <param name="percent">The percentage through the segment that the calculations should be made for from 0 == 0% to 1 == 100%</param>
/// <returns>Position at that point</returns>
public Vector3 CalculatePositionSpline(float percent)
{
return CatmullRom(m_prevPoi.transform.position, transform.position, m_nextPoi.transform.position, m_nextPoi.m_nextPoi.transform.position, percent);
}
/// <summary>
/// Calculate position based on the variables supplied. This must only be called after initialisation as it depends on the initialisation setup to work.
/// </summary>
/// <param name="percent">The percentage through the segment that the calculations should be made for from 0 == 0% to 1 == 100%</param>
/// <returns>Return the next position</returns>
public Vector3 CalculatePositionLinear(float percent)
{
//Ease the percentage
percent = m_positionEasingCalculator(percent);
//Handle no data
if (m_poiSteps.Count == 0)
{
return Vector3.zero;
}
//Handle only one item
if (m_poiSteps.Count == 1)
{
return m_poiSteps[0];
}
int maxSegments = m_poiSteps.Count - 1;
int firstSegment = (int)(percent * (float)maxSegments);
if (firstSegment == maxSegments)
{
return m_poiSteps[firstSegment];
}
float progress = (percent * (float)maxSegments) - (float)firstSegment;
//Debug.Log(string.Format("Pct is {0:0.000}, {1} {2}, progress is {3:0.000} lerp is {4:0.000} {5:0.000} ", percent, firstSegment, firstSegment+1, progress, Vector3.Lerp(m_poiSteps[(int)(firstSegment)], m_poiSteps[(int)(firstSegment)+1], progress).x, Vector3.Lerp(m_poiSteps[(int)(firstSegment)], m_poiSteps[(int)(firstSegment)+1], progress).z));
return Vector3.Lerp(m_poiSteps[firstSegment], m_poiSteps[firstSegment+1], progress);
}
/// <summary>
/// Calculate the velocity at the given position in the segment
/// </summary>
/// <param name="percent">Valuer between 0..1</param>
/// <returns>Velocity at that location</returns>
public float CalculateVelocity(float percent)
{
return Mathf.Lerp(m_startSpeed, m_nextPoi.m_startSpeed, m_velocityEasingCalculator(percent));
}
/// <summary>
/// Calculate the rotation at the given position in the segment
/// </summary>
/// <param name="percent">Value between 0..1</param>
/// <returns>Rotation at that location</returns>
public Quaternion CalculateRotation(float percent)
{
return Quaternion.Lerp(m_rotationStart, m_rotationEnd, m_rotationEasingCalculator(percent));
}
#endregion
#region Routines influenced by terrain
/// <summary>
/// Get the offsets of targetDistance, targetHeight relative to ground, and targetAngle of y rotation from source position to target position, as if they were on the same plane
/// </summary>
/// <param name="source">Source postiion</param>
/// <param name="target">Target position</param>
/// <param name="targetDistance">Distance from source to target as if on same plane</param>
/// <param name="targetHeight">Height of target relative to the terrain</param>
/// <param name="targetAngle">Angle from source to target, allowing only for rotation on y axis</param>
public void GetRelativeOffsets(Vector3 source, Vector3 target, out float targetDistance, out float targetHeight, out float targetAngle)
{
targetHeight = m_manager.GetValidatedLookatHeightRelativeToMinimum(target, m_heightCheckType);
Vector3 planarTargetPosition = new Vector3(target.x, source.y, target.z);
targetDistance = Vector3.Distance(source, planarTargetPosition);
Vector3 targetDirection = source - target;
if (targetDirection != Vector3.zero)
{
targetAngle = Quaternion.LookRotation(targetDirection, Vector3.up).eulerAngles.y;
}
else
{
targetAngle = 0;
}
}
#endregion
#region Handy general maths routines
/// <summary>
/// Return true if the values are approximately equal
/// </summary>
/// <param name="a">Parameter A</param>
/// <param name="b">Parameter B</param>
/// <returns>True if approximately equal</returns>
public static bool ApproximatelyEqual(float a, float b)
{
if (a == b || Mathf.Abs(a - b) < float.Epsilon)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// Rotate the point around the pivot - used to handle rotation
/// </summary>
/// <param name="point">Point to move</param>
/// <param name="pivot">Pivot</param>
/// <param name="angle">Angle to pivot</param>
/// <returns>New location</returns>
public static Vector3 RotatePointAroundPivot(Vector3 point, Vector3 pivot, Vector3 angle)
{
Vector3 dir = point - pivot;
dir = Quaternion.Euler(angle)*dir;
point = dir + pivot;
return point;
}
/// <summary>
/// Linear easing
/// </summary>
/// <param name="time"></param>
/// <param name="duration"></param>
/// <returns></returns>
private static float EaseLinear(float time, float duration = 1f)
{
return time/duration;
}
/// <summary>
/// Ease in
/// </summary>
/// <param name="time"></param>
/// <param name="duration"></param>
/// <returns></returns>
private static float EaseIn(float time, float duration = 1f)
{
return (time /= duration)*time;
}
/// <summary>
/// Ease out
/// </summary>
/// <param name="time"></param>
/// <param name="duration"></param>
/// <returns></returns>
private static float EaseOut(float time, float duration = 1f)
{
return -1f*(time /= duration)*(time - 2f);
}
/// <summary>
/// Ease in and out
/// </summary>
/// <param name="time"></param>
/// <param name="duration"></param>
/// <returns></returns>
private static float EaseInOut(float time, float duration = 1f)
{
if ((time /= duration/2f) < 1f)
return 0.5f*time*time;
return -0.5f*((--time)*(time - 2f) - 1f);
}
/// <summary>
/// Creates a new <see cref="Vector3"/> that contains CatmullRom interpolation of the specified vectors.
/// </summary>
/// <param name="value1">The first vector in interpolation.</param>
/// <param name="value2">The second vector in interpolation.</param>
/// <param name="value3">The third vector in interpolation.</param>
/// <param name="value4">The fourth vector in interpolation.</param>
/// <param name="amount">Weighting factor.</param>
/// <returns>The result of CatmullRom interpolation.</returns>
public static Vector3 CatmullRom(Vector3 value1, Vector3 value2, Vector3 value3, Vector3 value4, float amount)
{
return new Vector3(CalcCatmullRom(value1.x, value2.x, value3.x, value4.x, amount), CalcCatmullRom(value1.y, value2.y, value3.y, value4.y, amount), CalcCatmullRom(value1.z, value2.z, value3.z, value4.z, amount));
}
/// <summary>
/// Creates a new <see cref="Vector3"/> that contains CatmullRom interpolation of the specified vectors.
/// </summary>
/// <param name="value1">The first vector in interpolation.</param>
/// <param name="value2">The second vector in interpolation.</param>
/// <param name="value3">The third vector in interpolation.</param>
/// <param name="value4">The fourth vector in interpolation.</param>
/// <param name="amount">Weighting factor.</param>
/// <param name="result">The result of CatmullRom interpolation as an output parameter.</param>
public static void CatmullRom(ref Vector3 value1, ref Vector3 value2, ref Vector3 value3, ref Vector3 value4, float amount, out Vector3 result)
{
result.x = CalcCatmullRom(value1.x, value2.x, value3.x, value4.x, amount);
result.y = CalcCatmullRom(value1.y, value2.y, value3.y, value4.y, amount);
result.z = CalcCatmullRom(value1.z, value2.z, value3.z, value4.z, amount);
}
/// <summary>
/// Performs a Catmull-Rom interpolation using the specified positions.
/// </summary>
/// <param name="value1">The first position in the interpolation.</param>
/// <param name="value2">The second position in the interpolation.</param>
/// <param name="value3">The third position in the interpolation.</param>
/// <param name="value4">The fourth position in the interpolation.</param>
/// <param name="amount">Weighting factor.</param>
/// <returns>A position that is the result of the Catmull-Rom interpolation.</returns>
public static float CalcCatmullRom(float value1, float value2, float value3, float value4, float amount)
{
// Using formula from http://www.mvps.org/directx/articles/catmull/
// Internally using doubles not to lose precision
double amountSquared = amount*amount;
double amountCubed = amountSquared*amount;
return (float) (0.5*(2.0*value2 + (value3 - value1)*amount + (2.0*value1 - 5.0*value2 + 4.0*value3 - value4)*amountSquared + (3.0*value2 - value1 - 3.0*value3 + value4)*amountCubed));
}
/// <summary>
/// Creates a new <see cref="Vector3"/> that contains hermite spline interpolation.
/// </summary>
/// <param name="value1">The first position vector.</param>
/// <param name="tangent1">The first tangent vector.</param>
/// <param name="value2">The second position vector.</param>
/// <param name="tangent2">The second tangent vector.</param>
/// <param name="amount">Weighting factor.</param>
/// <returns>The hermite spline interpolation vector.</returns>
public static Vector3 Hermite(Vector3 value1, Vector3 tangent1, Vector3 value2, Vector3 tangent2, float amount)
{
return new Vector3(CalcHermite(value1.x, tangent1.x, value2.x, tangent2.x, amount), CalcHermite(value1.y, tangent1.y, value2.y, tangent2.y, amount), CalcHermite(value1.z, tangent1.z, value2.z, tangent2.z, amount));
}
/// <summary>
/// Creates a new <see cref="Vector3"/> that contains hermite spline interpolation.
/// </summary>
/// <param name="value1">The first position vector.</param>
/// <param name="tangent1">The first tangent vector.</param>
/// <param name="value2">The second position vector.</param>
/// <param name="tangent2">The second tangent vector.</param>
/// <param name="amount">Weighting factor.</param>
/// <param name="result">The hermite spline interpolation vector as an output parameter.</param>
public static void Hermite(ref Vector3 value1, ref Vector3 tangent1, ref Vector3 value2, ref Vector3 tangent2, float amount, out Vector3 result)
{
result.x = CalcHermite(value1.x, tangent1.x, value2.x, tangent2.x, amount);
result.y = CalcHermite(value1.y, tangent1.y, value2.y, tangent2.y, amount);
result.z = CalcHermite(value1.z, tangent1.z, value2.z, tangent2.z, amount);
}
/// <summary>
/// Performs a Hermite spline interpolation.
/// </summary>
/// <param name="value1">Source position.</param>
/// <param name="tangent1">Source tangent.</param>
/// <param name="value2">Source position.</param>
/// <param name="tangent2">Source tangent.</param>
/// <param name="amount">Weighting factor.</param>
/// <returns>The result of the Hermite spline interpolation.</returns>
public static float CalcHermite(float value1, float tangent1, float value2, float tangent2, float amount)
{
// All transformed to double not to lose precission
// Otherwise, for high numbers of param:amount the result is NaN instead of Infinity
double v1 = value1, v2 = value2, t1 = tangent1, t2 = tangent2, s = amount, result;
double sCubed = s*s*s;
double sSquared = s*s;
if (amount == 0f)
result = value1;
else if (amount == 1f)
result = value2;
else
result = (2*v1 - 2*v2 + t2 + t1)*sCubed + (3*v2 - 3*v1 - 2*t1 - t2)*sSquared + t1*s + v1;
return (float) result;
}
#endregion
}
}