1315 lines
51 KiB
C#
1315 lines
51 KiB
C#
using System;
|
|
using UnityEngine;
|
|
|
|
namespace AibisDream
|
|
{
|
|
[Serializable]
|
|
public class KiteWindSettings
|
|
{
|
|
[Header("平静漂移")]
|
|
[Tooltip("无风时缓慢上下左右游移的范围")]
|
|
public float calmFloatAmp = 0.46f;
|
|
[Tooltip("平静游移的基础频率;数值越小越从容")]
|
|
public float calmFloatFreq = 0.065f;
|
|
[Tooltip("有风时持续向风向一侧偏移的距离")]
|
|
public float steadyWindOffset = 0.45f;
|
|
[Tooltip("弱风时额外向下飘落的距离")]
|
|
public float weakWindDrop = 0.32f;
|
|
|
|
[Header("大风航迹")]
|
|
[Tooltip("乱度达到此值后开始明显靠近画面边缘")]
|
|
[Range(0f, 1f)] public float edgeWindThreshold = 0.48f;
|
|
[Tooltip("大风目标点最远可到达的横向距离")]
|
|
public float edgeTravel = 3.65f;
|
|
[Tooltip("大风目标点最远可到达的纵向距离")]
|
|
public float edgeTravelY = 1.35f;
|
|
[Tooltip("平静航迹的平滑时间")]
|
|
public float calmMotionSmoothTime = 1.2f;
|
|
[Tooltip("暴风航迹的平滑时间;仍保留惯性,不会瞬移")]
|
|
public float stormMotionSmoothTime = 0.68f;
|
|
[Tooltip("航迹最大速度,避免目标切换时突然抽走")]
|
|
public float maxTravelSpeed = 3f;
|
|
[Tooltip("风筝线允许的最大侧摆角度")]
|
|
public float maxTetherAngle = 62f;
|
|
[Tooltip("线张力把风筝拉向新平衡角的强度")]
|
|
public float tetherAngularStiffness = 2.4f;
|
|
[Tooltip("空气阻力和线阻尼;越大越不容易冲过头")]
|
|
public float tetherAngularDamping = 3.35f;
|
|
[Tooltip("最大角加速度,控制阵风起步有多猛")]
|
|
public float maxAngularAcceleration = 68f;
|
|
[Tooltip("最大角速度,控制横跨画面的速度上限")]
|
|
public float maxAngularSpeed = 48f;
|
|
[Tooltip("固定线长带来的边缘下沉弧度")]
|
|
public float tetherArcDrop = 1.15f;
|
|
[Tooltip("阵风纵向升沉的平滑时间")]
|
|
public float verticalGustSmoothTime = 0.85f;
|
|
|
|
[Header("大风与超级大风")]
|
|
[Tooltip("达到此强度后才允许左右换边;此前的大风主要顺着同一风向拉扯")]
|
|
[Range(0f, 1f)] public float superWindThreshold = 0.86f;
|
|
[Tooltip("大风时镜头追上后的最短停留时间")]
|
|
public float bigWindHoldMin = 2.6f;
|
|
[Tooltip("大风时镜头追上后的最长停留时间")]
|
|
public float bigWindHoldMax = 4.5f;
|
|
[Tooltip("超级大风时镜头追上后的最短停留时间")]
|
|
public float superWindHoldMin = 1.7f;
|
|
[Tooltip("超级大风时镜头追上后的最长停留时间")]
|
|
public float superWindHoldMax = 3f;
|
|
[Tooltip("两次捕捉之间至少经过的普通出镜次数")]
|
|
[Min(1)] public int doubleCatchIntervalMin = 3;
|
|
[Tooltip("两次捕捉之间至多经过的普通出镜次数")]
|
|
[Min(1)] public int doubleCatchIntervalMax = 5;
|
|
[Tooltip("双捕捉第一次出镜相对完整边缘行程的比例;镜头将要追上后才继续向外加速")]
|
|
[Range(0.5f, 0.9f)] public float doubleCatchFirstReach = 0.74f;
|
|
[Tooltip("双捕捉第二次加速相对完整边缘行程的最小比例")]
|
|
[Range(0.85f, 1f)] public float doubleCatchSecondReach = 0.96f;
|
|
|
|
[Header("风变大时的常驻表现")]
|
|
[Tooltip("低频风压推动风筝缓慢侧移的最大范围;不是机械抖动")]
|
|
public float windPressureAmp = 0.36f;
|
|
[Tooltip("风压起伏频率;0.1 约等于十秒一个主周期")]
|
|
public float windPressureFrequency = 0.11f;
|
|
[Tooltip("风压纵向位移相对于横向位移的比例")]
|
|
[Range(0f, 1f)] public float windPressureVerticalRatio = 0.48f;
|
|
[Tooltip("常驻风压位移带来的姿态响应")]
|
|
public float windPressurePoseGain = 3.2f;
|
|
|
|
[Header("升降曲线")]
|
|
[Tooltip("Yarn 指定升降时长的整体放慢倍率")]
|
|
public float heightDurationScale = 1.3f;
|
|
[Tooltip("每级高度变化带来的受风侧偏")]
|
|
public float heightArcPerLevel = 0.055f;
|
|
[Tooltip("升降侧偏的最大距离")]
|
|
public float heightArcMax = 0.42f;
|
|
[Tooltip("升降侧偏建立和回正的平滑时间")]
|
|
public float heightArcSmoothTime = 0.62f;
|
|
|
|
[Header("稳定姿态")]
|
|
[Tooltip("稳态迎风侧倾;参考实拍保持小角度")]
|
|
public float leanAmp = 3.2f;
|
|
[Tooltip("横向速度带来的轻微侧倾")]
|
|
public float bankFromVel = 1.25f;
|
|
[Tooltip("升降速度带来的轻微俯仰")]
|
|
public float pitchFromHeightVel = 1.6f;
|
|
[Tooltip("横向移动带来的轻微侧面透视")]
|
|
public float yawFromLateralVel = 1.15f;
|
|
[Tooltip("微小风压颤动幅度")]
|
|
public float flutterAmp = 0.55f;
|
|
[Tooltip("姿态变化的平滑时间")]
|
|
public float poseSmoothTime = 0.34f;
|
|
[Tooltip("任何风况下的最大姿态角,避免突然翻身")]
|
|
public float maxPoseAngle = 7f;
|
|
[Tooltip("俯仰和侧面透视的最大角度")]
|
|
public float maxPerspectiveTilt = 5.5f;
|
|
[Tooltip("有效风变化的平滑时间")]
|
|
public float windSmoothTime = 0.45f;
|
|
[Tooltip("高度对大风表现的增益")]
|
|
public float altitudeWindGain = 0.18f;
|
|
[Tooltip("尾部相位滞后")]
|
|
public float tailLagPhase = 0.9f;
|
|
[Tooltip("尾部幅度增益")]
|
|
public float tailGain = 1.2f;
|
|
}
|
|
|
|
[Serializable]
|
|
public class KiteCameraSettings
|
|
{
|
|
[Tooltip("摄影师看到风筝改变航迹后的反应延迟")]
|
|
public float reactionLatency = 0.34f;
|
|
[Tooltip("平静时镜头缓慢跟随的平滑时间")]
|
|
public float smoothTimePos = 0.92f;
|
|
[Tooltip("暴风时镜头追赶速度相对平静状态的倍率")]
|
|
[Range(0.25f, 1f)] public float stormFollowMultiplier = 0.58f;
|
|
[Tooltip("变焦追踪平滑;升降时风筝尺寸先变化,镜头再跟焦")]
|
|
public float smoothTimeZoom = 1.18f;
|
|
[Tooltip("最大变焦")]
|
|
public float maxZoom = 3.2f;
|
|
[Tooltip("镜头追踪目标允许的最大偏移")]
|
|
public float maxPan = 4.5f;
|
|
[Tooltip("高度变小时由镜头补回的缩放比例;低于 1 会让风筝越飞越小")]
|
|
[Range(0f, 1f)] public float zoomCompensation = 0.55f;
|
|
[Tooltip("平静阶段镜头跟随风筝纵向高度的比例")]
|
|
[Range(0f, 1f)] public float calmVerticalFollow = 0.42f;
|
|
[Tooltip("乱风阶段镜头跟随风筝纵向高度的比例")]
|
|
[Range(0f, 1f)] public float stormVerticalFollow = 0.78f;
|
|
[Tooltip("双捕捉中判定镜头快要框住风筝的构图误差")]
|
|
public float doubleCatchNearDistance = 0.48f;
|
|
[Tooltip("二次追回后镜头沿追赶方向多走的距离")]
|
|
public float catchOvershootDistance = 0.18f;
|
|
[Tooltip("二次追回后镜头过冲并收住的总时长")]
|
|
public float catchOvershootDuration = 0.45f;
|
|
}
|
|
|
|
[Serializable]
|
|
public class KiteFocusSettings
|
|
{
|
|
[Tooltip("最大虚焦")]
|
|
public float maxBlur = 0.68f;
|
|
[Tooltip("高斯半径 (_BlurSize)")]
|
|
public float blurSize = 0.011f;
|
|
[Tooltip("位置误差参考")]
|
|
public float posErrorRef = 0.7f;
|
|
[Tooltip("变焦误差参考")]
|
|
public float zoomErrorRef = 0.2f;
|
|
[Tooltip("低于此追踪误差完全不虚焦,避免平静漂移一直糊")]
|
|
[Range(0f, 0.95f)] public float blurDeadZone = 0.3f;
|
|
[Tooltip("失焦速度")]
|
|
public float blurInSpeed = 3.5f;
|
|
[Tooltip("重新合焦速度")]
|
|
public float blurOutSpeed = 0.95f;
|
|
[Tooltip("大移动结束触发一次轻微过焦的阈值")]
|
|
public float settleEnterThreshold = 0.48f;
|
|
[Tooltip("重新合焦阶段时长")]
|
|
public float settleDuration = 0.46f;
|
|
[Tooltip("重新合焦时轻微越过清晰点的幅度")]
|
|
public float settleBump = 0.08f;
|
|
public Vector3 settlePhaseRatios = new Vector3(0.5f, 0.2f, 0.3f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 连续航迹驱动的风筝表现:风筝自身保持稳定迎风姿态,速度感主要由延迟镜头追踪表现。
|
|
/// </summary>
|
|
public class KiteRigDriver : MonoBehaviour
|
|
{
|
|
private enum DoubleCatchPhase
|
|
{
|
|
None,
|
|
FirstEscape,
|
|
WaitingForFirstCatch,
|
|
SecondEscape,
|
|
WaitingForSecondCatch,
|
|
Overshooting,
|
|
}
|
|
|
|
private static readonly int BlurAmountId = Shader.PropertyToID("_BlurAmount");
|
|
private static readonly int BlurSizeId = Shader.PropertyToID("_BlurSize");
|
|
|
|
private const float MaxHeight = 13f;
|
|
private const float ScalePerHeight = 0.76f;
|
|
private const float MinScaleFactor = 0.12f;
|
|
private static readonly Vector3 TopDrift = new Vector3(0.55f, 1.35f, 0f);
|
|
|
|
private Transform _cameraNode;
|
|
private Transform _rig;
|
|
private Transform _sway;
|
|
private SpriteRenderer _renderer;
|
|
private Material _focusMaterial;
|
|
|
|
private Vector3 _framePosition;
|
|
private Quaternion _baseLocalRotation;
|
|
private Vector3 _baseLocalScale;
|
|
|
|
private KiteWindSettings _windSettings;
|
|
private KiteCameraSettings _cameraSettings;
|
|
private KiteFocusSettings _focusSettings;
|
|
|
|
private int _wind;
|
|
private float _turbulence;
|
|
private float _effectiveWind;
|
|
private float _effectiveWindVel;
|
|
|
|
private float _height;
|
|
private float _heightTarget;
|
|
private float _heightSmoothTime = 0.3f;
|
|
private float _heightVel;
|
|
private float _heightArc;
|
|
private float _heightArcTarget;
|
|
private float _heightArcVel;
|
|
|
|
private Vector3 _drift;
|
|
private Vector3 _driftVel;
|
|
private Vector3 _windPressure;
|
|
private Vector3 _routeTarget;
|
|
private float _routeRetargetAt;
|
|
private int _routeSide = 1;
|
|
private bool _routeMoveActive;
|
|
private bool _routeWaitingForCamera;
|
|
private bool _routeCountsTowardDoubleCatch;
|
|
private bool _wasSuperWind;
|
|
private int _normalEscapesUntilDoubleCatch;
|
|
private DoubleCatchPhase _doubleCatchPhase;
|
|
private float _routeAngle;
|
|
private float _routeAngleTarget;
|
|
private float _routeAngularVelocity;
|
|
private float _routeVertical;
|
|
private float _routeVerticalTarget;
|
|
private float _routeVerticalVel;
|
|
private float _poseAngle;
|
|
private float _poseAngleVel;
|
|
private float _pitchAngle;
|
|
private float _pitchAngleVel;
|
|
private float _yawAngle;
|
|
private float _yawAngleVel;
|
|
|
|
private Vector3 _camTrackPos;
|
|
private Vector3 _camTrackPosVel;
|
|
private float _camZoom = 1f;
|
|
private float _camZoomVel;
|
|
private Vector3 _camOvershootDirection;
|
|
private Vector3 _camOvershootOffset;
|
|
private float _camOvershootElapsed = -1f;
|
|
|
|
private float _blur;
|
|
private float _blurTarget;
|
|
private bool _wasLargeMove;
|
|
private float _settleElapsed = -1f;
|
|
private float _settleSharp;
|
|
|
|
private readonly PoseSample[] _poseRing = new PoseSample[96];
|
|
private int _poseWrite;
|
|
private int _poseCount;
|
|
private float _noiseSeedA;
|
|
private float _noiseSeedB;
|
|
private bool _configured;
|
|
private bool _running;
|
|
|
|
public float TailSway { get; private set; }
|
|
public float CurrentHeight => _height;
|
|
public float Turbulence => _turbulence;
|
|
public bool IsRunning => _running;
|
|
|
|
public void Configure(
|
|
Transform cameraNode,
|
|
Transform rig,
|
|
Transform sway,
|
|
SpriteRenderer renderer,
|
|
Vector3 framePosition,
|
|
Quaternion baseLocalRotation,
|
|
Vector3 baseLocalScale,
|
|
KiteWindSettings windSettings,
|
|
KiteCameraSettings cameraSettings,
|
|
KiteFocusSettings focusSettings,
|
|
Material focusMaterial)
|
|
{
|
|
_cameraNode = cameraNode;
|
|
_rig = rig;
|
|
_sway = sway;
|
|
_renderer = renderer;
|
|
_framePosition = framePosition;
|
|
_baseLocalRotation = baseLocalRotation;
|
|
_baseLocalScale = baseLocalScale;
|
|
_windSettings = windSettings ?? new KiteWindSettings();
|
|
_cameraSettings = cameraSettings ?? new KiteCameraSettings();
|
|
_focusSettings = focusSettings ?? new KiteFocusSettings();
|
|
_focusMaterial = focusMaterial;
|
|
_noiseSeedA = UnityEngine.Random.Range(0f, 100f);
|
|
_noiseSeedB = UnityEngine.Random.Range(0f, 100f);
|
|
_configured = true;
|
|
|
|
if (_renderer != null && _focusMaterial != null)
|
|
{
|
|
_renderer.material = _focusMaterial;
|
|
ApplyBlurToMaterial(0f);
|
|
}
|
|
}
|
|
|
|
public void Wake(float height, int wind)
|
|
{
|
|
if (!_configured) return;
|
|
|
|
enabled = true;
|
|
_running = true;
|
|
_wind = wind;
|
|
_turbulence = 0f;
|
|
_effectiveWind = WindBase(wind);
|
|
_effectiveWindVel = 0f;
|
|
_height = Mathf.Clamp(height, 0f, MaxHeight);
|
|
_heightTarget = _height;
|
|
_heightVel = 0f;
|
|
_heightArc = 0f;
|
|
_heightArcTarget = 0f;
|
|
_heightArcVel = 0f;
|
|
_drift = Vector3.zero;
|
|
_driftVel = Vector3.zero;
|
|
_windPressure = Vector3.zero;
|
|
_routeTarget = Vector3.zero;
|
|
_routeRetargetAt = Time.time + 1f;
|
|
_routeSide = wind < 0 ? -1 : 1;
|
|
_routeMoveActive = false;
|
|
_routeWaitingForCamera = false;
|
|
_routeCountsTowardDoubleCatch = false;
|
|
_wasSuperWind = false;
|
|
_doubleCatchPhase = DoubleCatchPhase.None;
|
|
ResetDoubleCatchCadence();
|
|
_routeAngle = 0f;
|
|
_routeAngleTarget = 0f;
|
|
_routeAngularVelocity = 0f;
|
|
_routeVertical = 0f;
|
|
_routeVerticalTarget = 0f;
|
|
_routeVerticalVel = 0f;
|
|
_poseAngle = 0f;
|
|
_poseAngleVel = 0f;
|
|
_pitchAngle = 0f;
|
|
_pitchAngleVel = 0f;
|
|
_yawAngle = 0f;
|
|
_yawAngleVel = 0f;
|
|
ResetCameraOvershoot();
|
|
_blur = 0f;
|
|
_blurTarget = 0f;
|
|
_wasLargeMove = false;
|
|
_settleElapsed = -1f;
|
|
_poseWrite = 0;
|
|
_poseCount = 0;
|
|
TailSway = 0f;
|
|
|
|
ApplyHeightPose(_height);
|
|
ResetSwayLocal();
|
|
ApplyMotionPose(Time.time, true, 0f);
|
|
SnapCameraToStable();
|
|
ApplyBlurToMaterial(0f);
|
|
}
|
|
|
|
public void SetHeight(float target, float duration)
|
|
{
|
|
if (!_running) return;
|
|
|
|
float nextHeight = Mathf.Clamp(target, 0f, MaxHeight);
|
|
float deltaHeight = nextHeight - _height;
|
|
_heightTarget = nextHeight;
|
|
|
|
float physicalDuration = Mathf.Max(0.05f, duration)
|
|
* Mathf.Max(0.2f, _windSettings.heightDurationScale);
|
|
_heightSmoothTime = Mathf.Max(0.04f, physicalDuration / 2.45f);
|
|
|
|
float windSide = Mathf.Abs(_effectiveWind) > 0.08f
|
|
? Mathf.Sign(_effectiveWind)
|
|
: (_routeSide == 0 ? 1f : _routeSide);
|
|
float arcMagnitude = Mathf.Min(
|
|
Mathf.Abs(deltaHeight) * _windSettings.heightArcPerLevel,
|
|
_windSettings.heightArcMax);
|
|
_heightArcTarget = windSide * arcMagnitude;
|
|
if (duration <= 0.001f)
|
|
{
|
|
_height = _heightTarget;
|
|
_heightVel = 0f;
|
|
_heightArc = 0f;
|
|
_heightArcTarget = 0f;
|
|
_heightArcVel = 0f;
|
|
}
|
|
}
|
|
|
|
public void SetWind(int wind, float turbulence = -1f)
|
|
{
|
|
if (!_running) return;
|
|
|
|
int previousWind = _wind;
|
|
float previousTurbulence = _turbulence;
|
|
_wind = wind;
|
|
if (turbulence >= 0f)
|
|
{
|
|
_turbulence = Mathf.Clamp01(turbulence);
|
|
}
|
|
|
|
// 只提前安排下一段航迹,不重置当前位置和速度。
|
|
// 因此 Yarn 连续切换风况时不会跳帧或瞬间反向。
|
|
if (previousWind != _wind || Mathf.Abs(previousTurbulence - _turbulence) > 0.05f)
|
|
{
|
|
_routeRetargetAt = Mathf.Min(_routeRetargetAt, Time.time + 0.2f);
|
|
}
|
|
}
|
|
|
|
public void SnapCameraToStable()
|
|
{
|
|
if (_cameraNode == null || _renderer == null) return;
|
|
|
|
Vector3 kitePosition = SampleKiteLocalPose();
|
|
float kiteScale = ScaleFactor(_height);
|
|
EvaluateCameraComposition(
|
|
kitePosition,
|
|
kiteScale,
|
|
MotionIntensity(),
|
|
out Vector3 positionTarget,
|
|
out float zoomTarget);
|
|
_camTrackPos = positionTarget;
|
|
_camTrackPosVel = Vector3.zero;
|
|
_camZoom = zoomTarget;
|
|
_camZoomVel = 0f;
|
|
ResetCameraOvershoot();
|
|
ApplyCameraTransform();
|
|
PushPoseSample(positionTarget, zoomTarget, Time.time);
|
|
}
|
|
|
|
public void ResetAndSleep()
|
|
{
|
|
_running = false;
|
|
enabled = false;
|
|
_effectiveWind = 0f;
|
|
_effectiveWindVel = 0f;
|
|
_drift = Vector3.zero;
|
|
_driftVel = Vector3.zero;
|
|
_heightArc = 0f;
|
|
_heightArcTarget = 0f;
|
|
_heightArcVel = 0f;
|
|
_routeTarget = Vector3.zero;
|
|
_routeMoveActive = false;
|
|
_routeWaitingForCamera = false;
|
|
_routeCountsTowardDoubleCatch = false;
|
|
_wasSuperWind = false;
|
|
_doubleCatchPhase = DoubleCatchPhase.None;
|
|
ResetDoubleCatchCadence();
|
|
_routeAngle = 0f;
|
|
_routeAngleTarget = 0f;
|
|
_routeAngularVelocity = 0f;
|
|
_routeVertical = 0f;
|
|
_routeVerticalTarget = 0f;
|
|
_routeVerticalVel = 0f;
|
|
_poseAngle = 0f;
|
|
_poseAngleVel = 0f;
|
|
_pitchAngle = 0f;
|
|
_pitchAngleVel = 0f;
|
|
_yawAngle = 0f;
|
|
_yawAngleVel = 0f;
|
|
ResetCameraOvershoot();
|
|
_blur = 0f;
|
|
_blurTarget = 0f;
|
|
_settleElapsed = -1f;
|
|
_wasLargeMove = false;
|
|
_poseCount = 0;
|
|
TailSway = 0f;
|
|
ResetSwayLocal();
|
|
ApplyBlurToMaterial(0f);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!_running || !_configured) return;
|
|
|
|
float dt = Mathf.Max(Time.deltaTime, 0.0001f);
|
|
float now = Time.time;
|
|
StepWind(dt, now);
|
|
StepHeight(dt);
|
|
StepMotion(dt, now);
|
|
StepCamera(dt, now);
|
|
StepFocus(dt);
|
|
}
|
|
|
|
private void StepWind(float dt, float now)
|
|
{
|
|
// 只使用低频风压变化;快速变化交给航迹目标,不直接写入速度或姿态。
|
|
float slowNoise = PerlinSigned(now * 0.08f, _noiseSeedA) * _turbulence * 0.16f;
|
|
float altitudeBoost = 1f + (_height / MaxHeight) * _windSettings.altitudeWindGain;
|
|
float raw = (WindBase(_wind) + slowNoise) * altitudeBoost;
|
|
_effectiveWind = Mathf.SmoothDamp(
|
|
_effectiveWind,
|
|
raw,
|
|
ref _effectiveWindVel,
|
|
Mathf.Max(0.05f, _windSettings.windSmoothTime),
|
|
Mathf.Infinity,
|
|
dt);
|
|
}
|
|
|
|
private void StepHeight(float dt)
|
|
{
|
|
if (Mathf.Abs(_height - _heightTarget) < 0.0005f)
|
|
{
|
|
_height = _heightTarget;
|
|
_heightVel = 0f;
|
|
_heightArcTarget = 0f;
|
|
}
|
|
else
|
|
{
|
|
_height = Mathf.SmoothDamp(
|
|
_height,
|
|
_heightTarget,
|
|
ref _heightVel,
|
|
Mathf.Max(0.02f, _heightSmoothTime),
|
|
Mathf.Infinity,
|
|
dt);
|
|
}
|
|
|
|
_heightArc = Mathf.SmoothDamp(
|
|
_heightArc,
|
|
_heightArcTarget,
|
|
ref _heightArcVel,
|
|
Mathf.Max(0.08f, _windSettings.heightArcSmoothTime),
|
|
Mathf.Infinity,
|
|
dt);
|
|
|
|
ApplyHeightPose(_height);
|
|
if (_renderer != null)
|
|
{
|
|
Color color = _renderer.color;
|
|
color.a = Alpha(_height);
|
|
_renderer.color = color;
|
|
}
|
|
}
|
|
|
|
private void StepMotion(float dt, float now)
|
|
{
|
|
if (_sway == null || _rig == null) return;
|
|
|
|
float intensity = MotionIntensity();
|
|
UpdateRoutePlan(intensity, now);
|
|
StepTetherPhysics(dt);
|
|
|
|
Vector3 calmTarget = SampleCalmDrift(now, intensity);
|
|
_windPressure = SampleWindPressure(now, intensity);
|
|
Vector3 windTarget = new Vector3(
|
|
_effectiveWind * _windSettings.steadyWindOffset,
|
|
_wind < 0 ? -_windSettings.weakWindDrop : Mathf.Abs(_effectiveWind) * 0.08f,
|
|
0f);
|
|
|
|
Vector3 target = calmTarget + windTarget + _windPressure + _routeTarget;
|
|
target.x = Mathf.Clamp(target.x, -_cameraSettings.maxPan, _cameraSettings.maxPan);
|
|
target.y = Mathf.Clamp(target.y, -_cameraSettings.maxPan * 0.72f, _cameraSettings.maxPan * 0.72f);
|
|
|
|
float smoothTime = Mathf.Lerp(
|
|
_windSettings.calmMotionSmoothTime,
|
|
_windSettings.stormMotionSmoothTime,
|
|
Smooth01(intensity));
|
|
_drift = Vector3.SmoothDamp(
|
|
_drift,
|
|
target,
|
|
ref _driftVel,
|
|
Mathf.Max(0.08f, smoothTime),
|
|
Mathf.Max(0.2f, _windSettings.maxTravelSpeed),
|
|
dt);
|
|
|
|
ApplyMotionPose(now, false, dt);
|
|
UpdateRouteArrival(intensity);
|
|
}
|
|
|
|
private void UpdateRoutePlan(float intensity, float now)
|
|
{
|
|
bool isSuperWind = intensity >= _windSettings.superWindThreshold;
|
|
if (intensity < _windSettings.edgeWindThreshold)
|
|
{
|
|
if (_wasSuperWind)
|
|
{
|
|
ResetDoubleCatchCadence();
|
|
}
|
|
|
|
_wasSuperWind = false;
|
|
_routeAngleTarget = 0f;
|
|
_routeVerticalTarget = 0f;
|
|
_routeMoveActive = false;
|
|
_routeWaitingForCamera = false;
|
|
_routeCountsTowardDoubleCatch = false;
|
|
_doubleCatchPhase = DoubleCatchPhase.None;
|
|
ResetCameraOvershoot();
|
|
_routeRetargetAt = now + UnityEngine.Random.Range(0.8f, 1.5f);
|
|
return;
|
|
}
|
|
|
|
if (_wasSuperWind && !isSuperWind)
|
|
{
|
|
CancelDoubleCatchForWindChange();
|
|
ResetDoubleCatchCadence();
|
|
}
|
|
else if (!_wasSuperWind && isSuperWind)
|
|
{
|
|
ResetDoubleCatchCadence();
|
|
}
|
|
|
|
_wasSuperWind = isSuperWind;
|
|
if (_routeMoveActive) return;
|
|
|
|
if (_doubleCatchPhase == DoubleCatchPhase.WaitingForFirstCatch)
|
|
{
|
|
if (!IsCameraNearCatch()) return;
|
|
|
|
ChooseNextRouteTarget(
|
|
intensity,
|
|
keepSide: true,
|
|
reachScale: Mathf.Clamp(_windSettings.doubleCatchSecondReach, 0.85f, 1f));
|
|
_doubleCatchPhase = DoubleCatchPhase.SecondEscape;
|
|
_routeMoveActive = true;
|
|
return;
|
|
}
|
|
|
|
if (_doubleCatchPhase == DoubleCatchPhase.WaitingForSecondCatch)
|
|
{
|
|
if (!IsCameraNearCatch()) return;
|
|
|
|
BeginCameraOvershoot();
|
|
return;
|
|
}
|
|
|
|
if (_doubleCatchPhase == DoubleCatchPhase.Overshooting) return;
|
|
|
|
if (_routeWaitingForCamera)
|
|
{
|
|
if (!IsCameraCaught()) return;
|
|
|
|
_routeWaitingForCamera = false;
|
|
if (_routeCountsTowardDoubleCatch && isSuperWind)
|
|
{
|
|
_normalEscapesUntilDoubleCatch = Mathf.Max(
|
|
0,
|
|
_normalEscapesUntilDoubleCatch - 1);
|
|
}
|
|
|
|
_routeCountsTowardDoubleCatch = false;
|
|
// 镜头回到风筝后再多停一拍,避免机械地一追上就立刻出发。
|
|
float superBlend = Smooth01(Mathf.InverseLerp(
|
|
_windSettings.superWindThreshold,
|
|
1f,
|
|
intensity));
|
|
float holdMin = Mathf.Lerp(
|
|
_windSettings.bigWindHoldMin,
|
|
_windSettings.superWindHoldMin,
|
|
superBlend);
|
|
float holdMax = Mathf.Lerp(
|
|
_windSettings.bigWindHoldMax,
|
|
_windSettings.superWindHoldMax,
|
|
superBlend);
|
|
holdMax = Mathf.Max(holdMin, holdMax);
|
|
_routeRetargetAt = now + UnityEngine.Random.Range(holdMin, holdMax);
|
|
return;
|
|
}
|
|
|
|
if (now >= _routeRetargetAt)
|
|
{
|
|
if (isSuperWind && _normalEscapesUntilDoubleCatch <= 0)
|
|
{
|
|
StartDoubleCatch(intensity);
|
|
}
|
|
else
|
|
{
|
|
ChooseNextRouteTarget(intensity, keepSide: false, reachScale: 1f);
|
|
_routeMoveActive = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void StartDoubleCatch(float intensity)
|
|
{
|
|
_routeWaitingForCamera = false;
|
|
_routeCountsTowardDoubleCatch = false;
|
|
ChooseNextRouteTarget(
|
|
intensity,
|
|
keepSide: false,
|
|
reachScale: Mathf.Clamp(_windSettings.doubleCatchFirstReach, 0.5f, 0.9f));
|
|
_doubleCatchPhase = DoubleCatchPhase.FirstEscape;
|
|
_routeMoveActive = true;
|
|
}
|
|
|
|
private void CancelDoubleCatchForWindChange()
|
|
{
|
|
if (_doubleCatchPhase == DoubleCatchPhase.None && _camOvershootElapsed < 0f) return;
|
|
|
|
_doubleCatchPhase = DoubleCatchPhase.None;
|
|
_routeMoveActive = false;
|
|
_routeWaitingForCamera = true;
|
|
_routeCountsTowardDoubleCatch = false;
|
|
_routeRetargetAt = float.PositiveInfinity;
|
|
ResetCameraOvershoot();
|
|
}
|
|
|
|
private void ResetDoubleCatchCadence()
|
|
{
|
|
int min = Mathf.Max(1, _windSettings != null ? _windSettings.doubleCatchIntervalMin : 3);
|
|
int max = Mathf.Max(min, _windSettings != null ? _windSettings.doubleCatchIntervalMax : 5);
|
|
_normalEscapesUntilDoubleCatch = UnityEngine.Random.Range(min, max + 1);
|
|
}
|
|
|
|
private void StepTetherPhysics(float dt)
|
|
{
|
|
float stiffness = Mathf.Max(0.1f, _windSettings.tetherAngularStiffness);
|
|
float damping = Mathf.Max(0f, _windSettings.tetherAngularDamping);
|
|
float angularAcceleration = (_routeAngleTarget - _routeAngle) * stiffness
|
|
- _routeAngularVelocity * damping;
|
|
angularAcceleration = Mathf.Clamp(
|
|
angularAcceleration,
|
|
-Mathf.Abs(_windSettings.maxAngularAcceleration),
|
|
Mathf.Abs(_windSettings.maxAngularAcceleration));
|
|
|
|
_routeAngularVelocity += angularAcceleration * dt;
|
|
_routeAngularVelocity = Mathf.Clamp(
|
|
_routeAngularVelocity,
|
|
-Mathf.Abs(_windSettings.maxAngularSpeed),
|
|
Mathf.Abs(_windSettings.maxAngularSpeed));
|
|
_routeAngle += _routeAngularVelocity * dt;
|
|
|
|
_routeVertical = Mathf.SmoothDamp(
|
|
_routeVertical,
|
|
_routeVerticalTarget,
|
|
ref _routeVerticalVel,
|
|
Mathf.Max(0.08f, _windSettings.verticalGustSmoothTime),
|
|
Mathf.Infinity,
|
|
dt);
|
|
|
|
// 固定线长约束:横向侧摆越大,垂直高度自然略降,轨迹形成圆弧。
|
|
float angleRadians = _routeAngle * Mathf.Deg2Rad;
|
|
float x = Mathf.Sin(angleRadians) * _windSettings.edgeTravel;
|
|
float y = (Mathf.Cos(angleRadians) - 1f) * _windSettings.tetherArcDrop
|
|
+ _routeVertical;
|
|
_routeTarget = new Vector3(x, y, 0f);
|
|
}
|
|
|
|
private void UpdateRouteArrival(float intensity)
|
|
{
|
|
if (!_routeMoveActive) return;
|
|
float angleError = Mathf.Abs(_routeAngleTarget - _routeAngle);
|
|
float verticalError = Mathf.Abs(_routeVerticalTarget - _routeVertical);
|
|
if (angleError > 2.2f
|
|
|| Mathf.Abs(_routeAngularVelocity) > 4.5f
|
|
|| verticalError > 0.12f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_routeMoveActive = false;
|
|
if (_doubleCatchPhase == DoubleCatchPhase.FirstEscape)
|
|
{
|
|
_doubleCatchPhase = DoubleCatchPhase.WaitingForFirstCatch;
|
|
_routeRetargetAt = float.PositiveInfinity;
|
|
return;
|
|
}
|
|
|
|
if (_doubleCatchPhase == DoubleCatchPhase.SecondEscape)
|
|
{
|
|
_doubleCatchPhase = DoubleCatchPhase.WaitingForSecondCatch;
|
|
_routeRetargetAt = float.PositiveInfinity;
|
|
return;
|
|
}
|
|
|
|
_routeWaitingForCamera = true;
|
|
_routeCountsTowardDoubleCatch = intensity >= _windSettings.superWindThreshold;
|
|
_routeRetargetAt = float.PositiveInfinity;
|
|
}
|
|
|
|
private bool IsCameraCaught()
|
|
{
|
|
if (_renderer == null) return true;
|
|
Vector3 kitePosition = SampleKiteLocalPose();
|
|
EvaluateCameraComposition(
|
|
kitePosition,
|
|
ScaleFactor(_height),
|
|
MotionIntensity(),
|
|
out Vector3 positionTarget,
|
|
out _);
|
|
return Vector3.Distance(positionTarget, _camTrackPos + _camOvershootOffset) < 0.24f
|
|
&& _camTrackPosVel.magnitude < 0.38f;
|
|
}
|
|
|
|
private bool IsCameraNearCatch()
|
|
{
|
|
if (_renderer == null) return true;
|
|
|
|
Vector3 kitePosition = SampleKiteLocalPose();
|
|
EvaluateCameraComposition(
|
|
kitePosition,
|
|
ScaleFactor(_height),
|
|
MotionIntensity(),
|
|
out Vector3 positionTarget,
|
|
out _);
|
|
float threshold = Mathf.Max(0.25f, _cameraSettings.doubleCatchNearDistance);
|
|
return Vector3.Distance(positionTarget, _camTrackPos + _camOvershootOffset) <= threshold;
|
|
}
|
|
|
|
private void ChooseNextRouteTarget(float intensity, bool keepSide, float reachScale)
|
|
{
|
|
float edge01 = Mathf.InverseLerp(_windSettings.edgeWindThreshold, 1f, intensity);
|
|
bool alternateSides = intensity >= _windSettings.superWindThreshold;
|
|
|
|
if (!keepSide && alternateSides)
|
|
{
|
|
_routeSide = -_routeSide;
|
|
if (_routeSide == 0) _routeSide = 1;
|
|
}
|
|
else if (!keepSide)
|
|
{
|
|
// 大风阶段主要顺风靠向同一侧,只有少量同侧高度变化。
|
|
_routeSide = _wind < 0 ? -1 : 1;
|
|
}
|
|
|
|
float targetAngle = Mathf.Lerp(
|
|
_windSettings.maxTetherAngle * 0.42f,
|
|
_windSettings.maxTetherAngle,
|
|
Smooth01(edge01));
|
|
_routeAngleTarget = _routeSide
|
|
* targetAngle
|
|
* Mathf.Clamp(reachScale, 0.1f, 1f)
|
|
* UnityEngine.Random.Range(0.94f, 1f);
|
|
|
|
float yDistance = Mathf.Lerp(
|
|
_windSettings.edgeTravelY * 0.3f,
|
|
_windSettings.edgeTravelY,
|
|
Smooth01(edge01));
|
|
float nextVertical = UnityEngine.Random.Range(-yDistance, yDistance) * 0.55f;
|
|
if (keepSide && Mathf.Abs(nextVertical - _routeVerticalTarget) < yDistance * 0.18f)
|
|
{
|
|
nextVertical = -Mathf.Sign(_routeVerticalTarget == 0f ? 1f : _routeVerticalTarget)
|
|
* Mathf.Max(yDistance * 0.22f, Mathf.Abs(nextVertical));
|
|
}
|
|
|
|
_routeVerticalTarget = nextVertical;
|
|
|
|
// 这里不安排下一次移动;到点后必须经过镜头追赶和随机停留阶段。
|
|
_routeRetargetAt = float.PositiveInfinity;
|
|
}
|
|
|
|
private Vector3 SampleCalmDrift(float now, float intensity)
|
|
{
|
|
float frequency = Mathf.Max(0.005f, _windSettings.calmFloatFreq)
|
|
* Mathf.Lerp(1f, 2.8f, intensity);
|
|
float amplitude = _windSettings.calmFloatAmp * Mathf.Lerp(1f, 1.45f, intensity);
|
|
float phaseA = _noiseSeedA * 0.13f;
|
|
float phaseB = _noiseSeedB * 0.17f;
|
|
float t = now * frequency * Mathf.PI * 2f;
|
|
|
|
// 正弦负责保证始终有运动,低权重连续噪声负责打破规律。
|
|
float x = Mathf.Sin(t + phaseA) * 0.56f
|
|
+ Mathf.Sin(t * 0.47f + phaseB) * 0.24f
|
|
+ PerlinSigned(now * frequency * 0.32f, _noiseSeedA + 3f) * 0.2f;
|
|
float y = Mathf.Cos(t * 0.73f + phaseB) * 0.52f
|
|
+ Mathf.Sin(t * 0.31f + phaseA) * 0.26f
|
|
+ PerlinSigned(now * frequency * 0.25f, _noiseSeedB + 5f) * 0.22f;
|
|
return new Vector3(x * amplitude, y * amplitude * 0.82f, 0f);
|
|
}
|
|
|
|
private Vector3 SampleWindPressure(float now, float intensity)
|
|
{
|
|
// 0.25 / 0.38 阶段就开始让风有存在感;到大风阶段逐渐成为主要常驻运动。
|
|
// 同一股低频风压同时驱动位置和姿态,避免每帧独立随机形成机械振动。
|
|
float superThreshold = Mathf.Max(0.2f, _windSettings.superWindThreshold);
|
|
float pressure01 = Mathf.InverseLerp(0.18f, superThreshold, intensity);
|
|
float pressureWeight = Mathf.Sqrt(Smooth01(pressure01));
|
|
if (pressureWeight <= 0.001f) return Vector3.zero;
|
|
|
|
float frequency = Mathf.Max(0.01f, _windSettings.windPressureFrequency);
|
|
float phase = now * frequency * Mathf.PI * 2f;
|
|
float lateral = Mathf.Sin(phase + _noiseSeedA * 0.19f) * 0.62f
|
|
+ Mathf.Sin(phase * 0.43f + _noiseSeedB * 0.23f) * 0.2f
|
|
+ PerlinSigned(now * frequency * 0.38f, _noiseSeedA + 41f) * 0.18f;
|
|
float vertical = Mathf.Sin(phase * 0.61f + _noiseSeedB * 0.23f) * 0.68f
|
|
+ PerlinSigned(now * frequency * 0.29f, _noiseSeedB + 47f) * 0.32f;
|
|
|
|
// 大范围甩动时仍保留少量风压,但把主要画面让给航迹动作。
|
|
float idleWeight = _routeMoveActive ? 0.35f : 1f;
|
|
float amplitude = _windSettings.windPressureAmp * pressureWeight * idleWeight;
|
|
return new Vector3(
|
|
lateral * amplitude,
|
|
vertical * amplitude * _windSettings.windPressureVerticalRatio,
|
|
0f);
|
|
}
|
|
|
|
private void ApplyMotionPose(float now, bool snap, float dt)
|
|
{
|
|
float intensity = MotionIntensity();
|
|
float microFlutter = PerlinSigned(now * Mathf.Lerp(0.28f, 0.72f, intensity), _noiseSeedB + 23f)
|
|
* _windSettings.flutterAmp
|
|
* Mathf.Lerp(0.25f, 1f, intensity);
|
|
float targetAngle = -_effectiveWind * _windSettings.leanAmp
|
|
- _driftVel.x * _windSettings.bankFromVel
|
|
- _windPressure.x * _windSettings.windPressurePoseGain
|
|
+ microFlutter;
|
|
targetAngle = Mathf.Clamp(
|
|
targetAngle,
|
|
-Mathf.Abs(_windSettings.maxPoseAngle),
|
|
Mathf.Abs(_windSettings.maxPoseAngle));
|
|
|
|
if (snap)
|
|
{
|
|
_poseAngle = targetAngle;
|
|
_poseAngleVel = 0f;
|
|
}
|
|
else
|
|
{
|
|
_poseAngle = Mathf.SmoothDampAngle(
|
|
_poseAngle,
|
|
targetAngle,
|
|
ref _poseAngleVel,
|
|
Mathf.Max(0.05f, _windSettings.poseSmoothTime),
|
|
Mathf.Infinity,
|
|
dt);
|
|
}
|
|
|
|
float maxTilt = Mathf.Abs(_windSettings.maxPerspectiveTilt);
|
|
float targetPitch = Mathf.Clamp(
|
|
-_heightVel * _windSettings.pitchFromHeightVel + _driftVel.y * 0.85f,
|
|
-maxTilt,
|
|
maxTilt);
|
|
float targetYaw = Mathf.Clamp(
|
|
-_driftVel.x * _windSettings.yawFromLateralVel,
|
|
-maxTilt,
|
|
maxTilt);
|
|
|
|
if (snap)
|
|
{
|
|
_pitchAngle = targetPitch;
|
|
_yawAngle = targetYaw;
|
|
_pitchAngleVel = 0f;
|
|
_yawAngleVel = 0f;
|
|
}
|
|
else
|
|
{
|
|
float tiltSmooth = Mathf.Max(0.08f, _windSettings.poseSmoothTime * 1.15f);
|
|
_pitchAngle = Mathf.SmoothDampAngle(
|
|
_pitchAngle, targetPitch, ref _pitchAngleVel, tiltSmooth, Mathf.Infinity, dt);
|
|
_yawAngle = Mathf.SmoothDampAngle(
|
|
_yawAngle, targetYaw, ref _yawAngleVel, tiltSmooth, Mathf.Infinity, dt);
|
|
}
|
|
|
|
_sway.localPosition = Vector3.zero;
|
|
_sway.localRotation = Quaternion.Euler(_pitchAngle, _yawAngle, _poseAngle);
|
|
TailSway = PerlinSigned(now * 0.9f - _windSettings.tailLagPhase, _noiseSeedA + 31f)
|
|
* (0.25f + intensity * _windSettings.tailGain);
|
|
|
|
Vector3 heightArcOffset = new Vector3(_heightArc, Mathf.Abs(_heightArc) * 0.12f, 0f);
|
|
_rig.localPosition = HeightPosition(_height) + heightArcOffset + _drift;
|
|
_rig.localRotation = _baseLocalRotation;
|
|
_rig.localScale = HeightScale(_height);
|
|
}
|
|
|
|
private float MotionIntensity()
|
|
{
|
|
float baseWind = _wind > 0 ? 0.12f : _wind < 0 ? 0.06f : 0f;
|
|
float altitude = (_height / MaxHeight) * _windSettings.altitudeWindGain * _turbulence;
|
|
return Mathf.Clamp01(Mathf.Max(baseWind, _turbulence + altitude));
|
|
}
|
|
|
|
private void BeginCameraOvershoot()
|
|
{
|
|
float verticalSign = Mathf.Sign(_routeVerticalTarget);
|
|
_camOvershootDirection = new Vector3(_routeSide, verticalSign * 0.18f, 0f).normalized;
|
|
_camOvershootOffset = Vector3.zero;
|
|
_camOvershootElapsed = 0f;
|
|
_doubleCatchPhase = DoubleCatchPhase.Overshooting;
|
|
_routeRetargetAt = float.PositiveInfinity;
|
|
}
|
|
|
|
private void StepCameraOvershoot(float dt)
|
|
{
|
|
if (_camOvershootElapsed < 0f) return;
|
|
|
|
float duration = Mathf.Max(0.01f, _cameraSettings.catchOvershootDuration);
|
|
_camOvershootElapsed += dt;
|
|
float progress = Mathf.Clamp01(_camOvershootElapsed / duration);
|
|
const float peakAt = 0.34f;
|
|
float envelope = progress <= peakAt
|
|
? Smooth01(progress / peakAt)
|
|
: 1f - Smooth01((progress - peakAt) / (1f - peakAt));
|
|
_camOvershootOffset = _camOvershootDirection
|
|
* Mathf.Max(0f, _cameraSettings.catchOvershootDistance)
|
|
* envelope;
|
|
|
|
if (progress < 1f) return;
|
|
|
|
ResetCameraOvershoot();
|
|
_doubleCatchPhase = DoubleCatchPhase.None;
|
|
_routeWaitingForCamera = true;
|
|
_routeCountsTowardDoubleCatch = false;
|
|
_routeRetargetAt = float.PositiveInfinity;
|
|
ResetDoubleCatchCadence();
|
|
}
|
|
|
|
private void ResetCameraOvershoot()
|
|
{
|
|
_camOvershootDirection = Vector3.zero;
|
|
_camOvershootOffset = Vector3.zero;
|
|
_camOvershootElapsed = -1f;
|
|
}
|
|
|
|
private void EvaluateCameraComposition(
|
|
Vector3 kitePosition,
|
|
float kiteScale,
|
|
float intensity,
|
|
out Vector3 positionTarget,
|
|
out float zoomTarget)
|
|
{
|
|
float verticalFollow = Mathf.Lerp(
|
|
Mathf.Clamp01(_cameraSettings.calmVerticalFollow),
|
|
Mathf.Clamp01(_cameraSettings.stormVerticalFollow),
|
|
Smooth01(intensity));
|
|
positionTarget = ClampPan(new Vector3(
|
|
kitePosition.x,
|
|
kitePosition.y * verticalFollow,
|
|
kitePosition.z));
|
|
|
|
float fullZoom = Mathf.Clamp(
|
|
1f / Mathf.Max(kiteScale, 0.0001f),
|
|
1f,
|
|
_cameraSettings.maxZoom);
|
|
zoomTarget = Mathf.Lerp(
|
|
1f,
|
|
fullZoom,
|
|
Mathf.Clamp01(_cameraSettings.zoomCompensation));
|
|
}
|
|
|
|
private void StepCamera(float dt, float now)
|
|
{
|
|
Vector3 kitePosition = SampleKiteLocalPose();
|
|
float kiteScale = ScaleFactor(_height);
|
|
float intensity = MotionIntensity();
|
|
EvaluateCameraComposition(
|
|
kitePosition,
|
|
kiteScale,
|
|
intensity,
|
|
out Vector3 positionTarget,
|
|
out float zoomTarget);
|
|
PushPoseSample(positionTarget, zoomTarget, now);
|
|
|
|
SampleDelayedPose(now, out Vector3 delayedPosition, out float delayedZoom);
|
|
delayedPosition = ClampPan(delayedPosition);
|
|
|
|
// 平静时镜头从容地跟;风越大,摄影师越积极追赶快速换边的风筝。
|
|
float followMultiplier = Mathf.Lerp(
|
|
1f,
|
|
Mathf.Clamp(_cameraSettings.stormFollowMultiplier, 0.25f, 1f),
|
|
Smooth01(intensity));
|
|
float positionSmooth = _cameraSettings.smoothTimePos * followMultiplier;
|
|
|
|
_camTrackPos = Vector3.SmoothDamp(
|
|
_camTrackPos,
|
|
delayedPosition,
|
|
ref _camTrackPosVel,
|
|
Mathf.Max(0.03f, positionSmooth),
|
|
Mathf.Infinity,
|
|
dt);
|
|
_camZoom = Mathf.SmoothDamp(
|
|
_camZoom,
|
|
delayedZoom,
|
|
ref _camZoomVel,
|
|
Mathf.Max(0.03f, _cameraSettings.smoothTimeZoom),
|
|
Mathf.Infinity,
|
|
dt);
|
|
_camZoom = Mathf.Clamp(_camZoom, 1f, _cameraSettings.maxZoom);
|
|
|
|
StepCameraOvershoot(dt);
|
|
ApplyCameraTransform();
|
|
UpdateFocusTarget(positionTarget, zoomTarget);
|
|
}
|
|
|
|
private void UpdateFocusTarget(Vector3 positionTarget, float zoomTarget)
|
|
{
|
|
float positionError = Vector3.Distance(
|
|
positionTarget,
|
|
_camTrackPos + _camOvershootOffset)
|
|
/ Mathf.Max(0.01f, _focusSettings.posErrorRef);
|
|
float zoomError = Mathf.Abs(Mathf.Log(
|
|
Mathf.Max(zoomTarget, 0.001f) / Mathf.Max(_camZoom, 0.001f)))
|
|
/ Mathf.Max(0.01f, _focusSettings.zoomErrorRef);
|
|
float error = Mathf.Clamp01(positionError + zoomError);
|
|
|
|
float deadZone = Mathf.Clamp01(_focusSettings.blurDeadZone);
|
|
float gatedError = error <= deadZone
|
|
? 0f
|
|
: (error - deadZone) / Mathf.Max(0.001f, 1f - deadZone);
|
|
_blurTarget = _focusSettings.maxBlur * Mathf.Clamp01(gatedError);
|
|
|
|
bool largeMove = error > _focusSettings.settleEnterThreshold;
|
|
if (_wasLargeMove && !largeMove && _settleElapsed < 0f && _blur > 0.02f)
|
|
{
|
|
_settleElapsed = 0f;
|
|
_settleSharp = 0f;
|
|
}
|
|
|
|
_wasLargeMove = largeMove;
|
|
}
|
|
|
|
private void StepFocus(float dt)
|
|
{
|
|
float target = _settleElapsed >= 0f ? EvaluateSettleBlur(dt) : _blurTarget;
|
|
float speed = target > _blur ? _focusSettings.blurInSpeed : _focusSettings.blurOutSpeed;
|
|
_blur = Mathf.MoveTowards(
|
|
_blur,
|
|
target,
|
|
speed * dt * Mathf.Max(0.01f, _focusSettings.maxBlur));
|
|
ApplyBlurToMaterial(_blur);
|
|
}
|
|
|
|
private float EvaluateSettleBlur(float dt)
|
|
{
|
|
_settleElapsed += dt;
|
|
Vector3 ratios = _focusSettings.settlePhaseRatios;
|
|
float sum = ratios.x + ratios.y + ratios.z;
|
|
if (sum <= 0.001f)
|
|
{
|
|
ratios = new Vector3(0.5f, 0.2f, 0.3f);
|
|
sum = 1f;
|
|
}
|
|
|
|
float total = Mathf.Max(0.05f, _focusSettings.settleDuration);
|
|
float sharpenDuration = total * (ratios.x / sum);
|
|
float bumpDuration = total * (ratios.y / sum);
|
|
float recoverDuration = total * (ratios.z / sum);
|
|
float bump = Mathf.Clamp(_focusSettings.settleBump, 0f, _focusSettings.maxBlur);
|
|
|
|
if (_settleElapsed <= sharpenDuration)
|
|
{
|
|
float u = sharpenDuration <= 0.001f ? 1f : _settleElapsed / sharpenDuration;
|
|
return Mathf.Lerp(_blur, _settleSharp, Smooth01(u));
|
|
}
|
|
|
|
if (_settleElapsed <= sharpenDuration + bumpDuration)
|
|
{
|
|
float u = bumpDuration <= 0.001f
|
|
? 1f
|
|
: (_settleElapsed - sharpenDuration) / bumpDuration;
|
|
return Mathf.Lerp(_settleSharp, bump, Smooth01(u));
|
|
}
|
|
|
|
if (_settleElapsed <= sharpenDuration + bumpDuration + recoverDuration)
|
|
{
|
|
float u = recoverDuration <= 0.001f
|
|
? 1f
|
|
: (_settleElapsed - sharpenDuration - bumpDuration) / recoverDuration;
|
|
return Mathf.Lerp(bump, _settleSharp, Smooth01(u));
|
|
}
|
|
|
|
_settleElapsed = -1f;
|
|
return _settleSharp;
|
|
}
|
|
|
|
private void ApplyHeightPose(float height)
|
|
{
|
|
if (_rig == null) return;
|
|
_rig.localRotation = _baseLocalRotation;
|
|
_rig.localScale = HeightScale(height);
|
|
}
|
|
|
|
private void ApplyCameraTransform()
|
|
{
|
|
if (_cameraNode == null) return;
|
|
_cameraNode.localScale = Vector3.one * _camZoom;
|
|
_cameraNode.localRotation = Quaternion.identity;
|
|
_cameraNode.localPosition = _framePosition
|
|
- _camZoom * (_camTrackPos + _camOvershootOffset);
|
|
}
|
|
|
|
private Vector3 SampleKiteLocalPose()
|
|
{
|
|
if (_cameraNode == null || _renderer == null) return Vector3.zero;
|
|
return _cameraNode.InverseTransformPoint(_renderer.transform.position);
|
|
}
|
|
|
|
private void PushPoseSample(Vector3 position, float zoom, float time)
|
|
{
|
|
_poseRing[_poseWrite] = new PoseSample { position = position, zoom = zoom, time = time };
|
|
_poseWrite = (_poseWrite + 1) % _poseRing.Length;
|
|
if (_poseCount < _poseRing.Length) _poseCount++;
|
|
}
|
|
|
|
private void SampleDelayedPose(float now, out Vector3 position, out float zoom)
|
|
{
|
|
float targetTime = now - Mathf.Max(0f, _cameraSettings.reactionLatency);
|
|
if (_poseCount <= 0)
|
|
{
|
|
position = _camTrackPos;
|
|
zoom = _camZoom;
|
|
return;
|
|
}
|
|
|
|
int oldest = (_poseWrite - _poseCount + _poseRing.Length) % _poseRing.Length;
|
|
PoseSample before = _poseRing[oldest];
|
|
PoseSample after = before;
|
|
bool foundAfter = false;
|
|
|
|
for (int i = 0; i < _poseCount; i++)
|
|
{
|
|
int index = (oldest + i) % _poseRing.Length;
|
|
PoseSample sample = _poseRing[index];
|
|
if (sample.time <= targetTime)
|
|
{
|
|
before = sample;
|
|
after = sample;
|
|
continue;
|
|
}
|
|
|
|
after = sample;
|
|
foundAfter = true;
|
|
break;
|
|
}
|
|
|
|
if (!foundAfter || after.time <= before.time + 0.0001f)
|
|
{
|
|
position = before.position;
|
|
zoom = before.zoom;
|
|
return;
|
|
}
|
|
|
|
float t = Mathf.InverseLerp(before.time, after.time, targetTime);
|
|
position = Vector3.LerpUnclamped(before.position, after.position, t);
|
|
zoom = Mathf.LerpUnclamped(before.zoom, after.zoom, t);
|
|
}
|
|
|
|
private Vector3 ClampPan(Vector3 position)
|
|
{
|
|
float maxPan = _cameraSettings.maxPan;
|
|
if (maxPan <= 0.001f) return position;
|
|
position.x = Mathf.Clamp(position.x, -maxPan, maxPan);
|
|
position.y = Mathf.Clamp(position.y, -maxPan, maxPan);
|
|
return position;
|
|
}
|
|
|
|
private void ResetSwayLocal()
|
|
{
|
|
if (_sway == null) return;
|
|
_sway.localPosition = Vector3.zero;
|
|
_sway.localRotation = Quaternion.identity;
|
|
}
|
|
|
|
private void ApplyBlurToMaterial(float amount)
|
|
{
|
|
if (_renderer == null) return;
|
|
Material material = Application.isPlaying ? _renderer.material : _renderer.sharedMaterial;
|
|
if (material == null) return;
|
|
if (material.HasProperty(BlurAmountId)) material.SetFloat(BlurAmountId, amount);
|
|
if (material.HasProperty(BlurSizeId))
|
|
{
|
|
material.SetFloat(BlurSizeId, _focusSettings != null ? _focusSettings.blurSize : 0.011f);
|
|
}
|
|
}
|
|
|
|
private static float WindBase(int wind)
|
|
{
|
|
if (wind > 0) return 1f;
|
|
if (wind < 0) return -0.45f;
|
|
return 0f;
|
|
}
|
|
|
|
private static float PerlinSigned(float t, float seed)
|
|
{
|
|
return Mathf.PerlinNoise(t + seed, seed * 0.37f) * 2f - 1f;
|
|
}
|
|
|
|
private static float Smooth01(float t)
|
|
{
|
|
t = Mathf.Clamp01(t);
|
|
return t * t * (3f - 2f * t);
|
|
}
|
|
|
|
public static float ScaleFactor(float height)
|
|
{
|
|
return Mathf.Max(Mathf.Pow(ScalePerHeight, height), MinScaleFactor);
|
|
}
|
|
|
|
public Vector3 HeightPosition(float height)
|
|
{
|
|
return TopDrift * (Mathf.Clamp(height, 0f, MaxHeight) / MaxHeight);
|
|
}
|
|
|
|
public Vector3 HeightScale(float height)
|
|
{
|
|
return _baseLocalScale * ScaleFactor(height);
|
|
}
|
|
|
|
public static float Alpha(float height)
|
|
{
|
|
return height <= 8f
|
|
? 1f
|
|
: Mathf.Lerp(1f, 0.75f, (height - 8f) / (MaxHeight - 8f));
|
|
}
|
|
|
|
private struct PoseSample
|
|
{
|
|
public Vector3 position;
|
|
public float zoom;
|
|
public float time;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
if (_cameraNode == null) return;
|
|
Gizmos.color = new Color(0.3f, 0.85f, 1f, 0.7f);
|
|
Gizmos.DrawWireSphere(_cameraNode.TransformPoint(_camTrackPos), 0.05f);
|
|
Gizmos.color = new Color(1f, 0.6f, 0.2f, 0.8f);
|
|
if (_renderer != null) Gizmos.DrawWireSphere(_renderer.transform.position, 0.04f);
|
|
}
|
|
#endif
|
|
}
|
|
}
|