Files
CypherCore/Source/Game/Movement/MoveSpline.cs
T
2018-01-02 13:02:21 -05:00

422 lines
16 KiB
C#

/*
* Copyright (C) 2012-2018 CypherCore <http://github.com/CypherCore>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Framework.Dynamic;
using Framework.GameMath;
using System;
using System.Collections.Generic;
namespace Game.Movement
{
public class MoveSpline
{
public MoveSpline()
{
m_Id = 0;
time_passed = 0;
vertical_acceleration = 0.0f;
initialOrientation = 0.0f;
effect_start_time = 0;
point_Idx = 0;
point_Idx_offset = 0;
onTransport = false;
splineIsFacingOnly = false;
splineflags.Flags = SplineFlag.Done;
}
public void Initialize(MoveSplineInitArgs args)
{
splineflags = args.flags;
facing = args.facing;
m_Id = args.splineId;
point_Idx_offset = args.path_Idx_offset;
initialOrientation = args.initialOrientation;
time_passed = 0;
vertical_acceleration = 0.0f;
effect_start_time = 0;
spell_effect_extra = args.spellEffectExtra;
splineIsFacingOnly = args.path.Length == 2 && args.facing.type != MonsterMoveType.Normal && ((args.path[1] - args.path[0]).GetLength() < 0.1f);
// Check if its a stop spline
if (args.flags.hasFlag(SplineFlag.Done))
{
spline.clear();
return;
}
init_spline(args);
// init parabolic / animation
// spline initialized, duration known and i able to compute parabolic acceleration
if (args.flags.hasFlag(SplineFlag.Parabolic | SplineFlag.Animation | SplineFlag.FadeObject))
{
effect_start_time = (int)(Duration() * args.time_perc);
if (args.flags.hasFlag(SplineFlag.Parabolic) && effect_start_time < Duration())
{
float f_duration = (float)TimeSpan.FromMilliseconds(Duration() - effect_start_time).TotalSeconds;
vertical_acceleration = args.parabolic_amplitude * 8.0f / (f_duration * f_duration);
}
}
}
void init_spline(MoveSplineInitArgs args)
{
Spline.EvaluationMode[] modes = new Spline.EvaluationMode[2] { Spline.EvaluationMode.Linear, Spline.EvaluationMode.Catmullrom };
if (args.flags.hasFlag(SplineFlag.Cyclic))
{
spline.init_cyclic_spline(args.path, args.path.Length, modes[Convert.ToInt32(args.flags.isSmooth())], 0);
}
else
{
spline.Init_Spline(args.path, args.path.Length, modes[Convert.ToInt32(args.flags.isSmooth())]);
}
// init spline timestamps
if (splineflags.hasFlag(SplineFlag.Falling))
{
FallInitializer init = new FallInitializer(spline.getPoint(spline.first()).Z);
spline.initLengths(init);
}
else
{
CommonInitializer init = new CommonInitializer(args.velocity);
spline.initLengths(init);
}
// TODO: what to do in such cases? problem is in input data (all points are at same coords)
if (spline.length() < 1)
{
Log.outError(LogFilter.Unit, "MoveSpline.init_spline: zero length spline, wrong input data?");
spline.set_length(spline.last(), spline.isCyclic() ? 1000 : 1);
}
point_Idx = spline.first();
}
public int currentPathIdx()
{
int point = point_Idx_offset + point_Idx - spline.first() + (Finalized() ? 1 : 0);
if (isCyclic())
point = point % (spline.last() - spline.first());
return point;
}
public Vector3[] getPath() { return spline.getPoints(); }
public int timePassed() { return time_passed; }
public int Duration() { return spline.length(); }
public int _currentSplineIdx() { return point_Idx; }
public uint GetId() { return m_Id; }
public bool Finalized() { return splineflags.hasFlag(SplineFlag.Done); }
void _Finalize()
{
splineflags.SetUnsetFlag(SplineFlag.Done);
point_Idx = spline.last() - 1;
time_passed = Duration();
}
public Vector4 computePosition(int time_point, int point_index)
{
float u = 1.0f;
int seg_time = spline.length(point_index, point_index + 1);
if (seg_time > 0)
u = (time_point - spline.length(point_index)) / (float)seg_time;
Vector3 c;
float orientation = initialOrientation;
spline.Evaluate_Percent(point_index, u, out c);
if (splineflags.hasFlag(SplineFlag.Parabolic))
computeParabolicElevation(time_point, ref c.Z);
else if (splineflags.hasFlag(SplineFlag.Falling))
computeFallElevation(time_point, ref c.Z);
if (splineflags.hasFlag(SplineFlag.Done) && facing.type != MonsterMoveType.Normal)
{
if (facing.type == MonsterMoveType.FacingAngle)
orientation = facing.angle;
else if (facing.type == MonsterMoveType.FacingSpot)
orientation = (float)Math.Atan2(facing.f.Y - c.Y, facing.f.X - c.X);
//nothing to do for MoveSplineFlag.Final_Target flag
}
else
{
if (!splineflags.hasFlag(SplineFlag.OrientationFixed | SplineFlag.Falling | SplineFlag.Unknown0))
{
Vector3 hermite;
spline.Evaluate_Derivative(point_Idx, u, out hermite);
orientation = (float)Math.Atan2(hermite.Y, hermite.X);
}
if (splineflags.hasFlag(SplineFlag.Backward))
orientation = orientation - (float)Math.PI;
}
return new Vector4(c.X, c.Y, c.Z, orientation);
}
public Vector4 ComputePosition()
{
return computePosition(time_passed, point_Idx);
}
public Vector4 ComputePosition(int time_offset)
{
int time_point = time_passed + time_offset;
if (time_point >= Duration())
return computePosition(Duration(), spline.last() - 1);
if (time_point <= 0)
return computePosition(0, spline.first());
// find point_index where spline.length(point_index) < time_point < spline.length(point_index + 1)
int point_index = point_Idx;
while (time_point >= spline.length(point_index + 1))
++point_index;
while (time_point < spline.length(point_index))
--point_index;
return computePosition(time_point, point_index);
}
public void computeParabolicElevation(int time_point, ref float el)
{
if (time_point > effect_start_time)
{
float t_passedf = MSToSec((uint)(time_point - effect_start_time));
float t_durationf = MSToSec((uint)(Duration() - effect_start_time)); //client use not modified duration here
if (spell_effect_extra.HasValue && spell_effect_extra.Value.ParabolicCurveId != 0)
t_passedf *= Global.DB2Mgr.GetCurveValueAt(spell_effect_extra.Value.ParabolicCurveId, time_point / Duration());
el += (t_durationf - t_passedf) * 0.5f * vertical_acceleration * t_passedf;
}
}
public void computeFallElevation(int time_point, ref float el)
{
float z_now = spline.getPoint(spline.first()).Z - computeFallElevation(MSToSec((uint)time_point), false);
float final_z = FinalDestination().Z;
el = Math.Max(z_now, final_z);
}
public static float computeFallElevation(float t_passed, bool isSafeFall, float start_velocity = 0.0f)
{
float termVel;
float result;
if (isSafeFall)
termVel = SharedConst.terminalSafefallVelocity;
else
termVel = SharedConst.terminalVelocity;
if (start_velocity > termVel)
start_velocity = termVel;
float terminal_time = (float)((isSafeFall ? SharedConst.terminal_safeFall_fallTime : SharedConst.terminal_fallTime) - start_velocity / SharedConst.gravity); // the time that needed to reach terminalVelocity
if (t_passed > terminal_time)
{
result = termVel * (t_passed - terminal_time) +
start_velocity * terminal_time +
(float)SharedConst.gravity * terminal_time * terminal_time * 0.5f;
}
else
result = t_passed * (float)(start_velocity + t_passed * SharedConst.gravity * 0.5f);
return result;
}
float MSToSec(uint ms)
{
return ms / 1000.0f;
}
public void Interrupt() { splineflags.SetUnsetFlag(SplineFlag.Done); }
public void updateState(int difftime)
{
do
{
_updateState(ref difftime);
} while (difftime > 0);
}
UpdateResult _updateState(ref int ms_time_diff)
{
if (Finalized())
{
ms_time_diff = 0;
return UpdateResult.Arrived;
}
UpdateResult result = UpdateResult.None;
int minimal_diff = Math.Min(ms_time_diff, segment_time_elapsed());
time_passed += minimal_diff;
ms_time_diff -= minimal_diff;
if (time_passed >= next_timestamp())
{
++point_Idx;
if (point_Idx < spline.last())
{
result = UpdateResult.NextSegment;
}
else
{
if (spline.isCyclic())
{
point_Idx = spline.first();
time_passed = time_passed % Duration();
result = UpdateResult.NextCycle;
}
else
{
_Finalize();
ms_time_diff = 0;
result = UpdateResult.Arrived;
}
}
}
return result;
}
int next_timestamp() { return spline.length(point_Idx + 1); }
int segment_time_elapsed() { return next_timestamp() - time_passed; }
public bool isCyclic() { return splineflags.hasFlag(SplineFlag.Cyclic); }
public bool isFalling() { return splineflags.hasFlag(SplineFlag.Falling); }
public bool Initialized() { return !spline.empty(); }
public Vector3 FinalDestination() { return Initialized() ? spline.getPoint(spline.last()) : new Vector3(); }
#region Fields
public MoveSplineInitArgs InitArgs;
public Spline spline = new Spline();
public FacingInfo facing;
public MoveSplineFlag splineflags = new MoveSplineFlag();
public bool onTransport;
public bool splineIsFacingOnly;
public uint m_Id;
public int time_passed;
public float vertical_acceleration;
public float initialOrientation;
public int effect_start_time;
public int point_Idx;
public int point_Idx_offset;
public Optional<SpellEffectExtraData> spell_effect_extra;
#endregion
public class CommonInitializer : Initializer
{
public CommonInitializer(float _velocity)
{
velocityInv = 1000f / _velocity;
time = 1;
}
public float velocityInv;
public int time;
public int SetGetTime(Spline s, int i)
{
time += (int)(s.SegLength(i) * velocityInv);
return time;
}
}
public class FallInitializer : Initializer
{
public FallInitializer(float startelevation)
{
startElevation = startelevation;
}
float startElevation;
public int SetGetTime(Spline s, int i)
{
return (int)(computeFallTime(startElevation - s.getPoint(i + 1).Z, false) * 1000.0f);
}
float computeFallTime(float path_length, bool isSafeFall)
{
if (path_length < 0.0f)
return 0.0f;
float time;
if (isSafeFall)
{
if (path_length >= SharedConst.terminal_safeFall_length)
time = (path_length - SharedConst.terminal_safeFall_length) / SharedConst.terminalSafefallVelocity + SharedConst.terminal_safeFall_fallTime;
else
time = (float)Math.Sqrt(2.0f * path_length / SharedConst.gravity);
}
else
{
if (path_length >= SharedConst.terminal_length)
time = (path_length - SharedConst.terminal_length) / SharedConst.terminalVelocity + SharedConst.terminal_fallTime;
else
time = (float)Math.Sqrt(2.0f * path_length / SharedConst.gravity);
}
return time;
}
}
public enum UpdateResult
{
None = 0x01,
Arrived = 0x02,
NextCycle = 0x04,
NextSegment = 0x08
}
}
public interface Initializer
{
int SetGetTime(Spline s, int i);
}
public class SplineChainLink
{
public SplineChainLink(Vector3[] points, uint expectedDuration, uint msToNext)
{
Points.AddRange(points);
ExpectedDuration = expectedDuration;
TimeToNext = msToNext;
}
public SplineChainLink(uint expectedDuration, uint msToNext)
{
ExpectedDuration = expectedDuration;
TimeToNext = msToNext;
}
public List<Vector3> Points = new List<Vector3>();
public uint ExpectedDuration;
public uint TimeToNext;
}
public class SplineChainResumeInfo
{
public SplineChainResumeInfo() { }
public SplineChainResumeInfo(uint id, List<SplineChainLink> chain, bool walk, byte splineIndex, byte wpIndex, uint msToNext)
{
PointID = id;
Chain = chain;
IsWalkMode = walk;
SplineIndex = splineIndex;
PointIndex = wpIndex;
TimeToNext = msToNext;
}
public bool Empty() { return Chain.Empty(); }
public void Clear() { Chain.Clear(); }
public uint PointID;
public List<SplineChainLink> Chain = new List<SplineChainLink>();
public bool IsWalkMode;
public byte SplineIndex;
public byte PointIndex;
public uint TimeToNext;
}
}