Files
aibis-dream/Assets/Scripts/FixSystemNew/WaveformLine.cs
T
2024-12-05 12:05:13 +08:00

378 lines
12 KiB
C#

using System;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;
namespace AibisDream.FixSystem
{
public class WaveformLine : MonoBehaviour
{
#region 组件索引
private LineRenderer _waveformLine;
#endregion
private float _screenWidth;
private float _screenHeight;
#region 状态
private bool _isWavePlaying;
#endregion
private WaveParam _waveParam;
#region Line数据
private float[] _waveformData;
private float[] _noiseData;
private float[] _spikeData;
private float[] _displayData; // 最后展示数据
#endregion
[SerializeField] private WaveformConfig config;
private void Awake()
{
InitComponentRefs();
InitValues();
}
private void InitValues()
{
_waveformData = new float[config.samples];
_noiseData = new float[config.samples];
_spikeData = new float[config.samples];
_displayData = new float[config.samples];
_waveformLine.positionCount = config.samples;
_waveformLine.enabled = false;
}
private void InitComponentRefs()
{
_waveformLine = GetComponent<LineRenderer>();
}
private void Update()
{
if (_isWavePlaying)
{
// 绘制线条
DrawWaveform();
}
}
private void DrawWaveform()
{
// 计算当前线条的点数据
CalcLinePointsData();
// 计算结束,绘制线条
DrawLine();
}
#region 波形数据计算
private void CalcLinePointsData()
{
// 分组计算:
var waveformData = _waveParam.waveformType == WaveformType.Default
? GenerateNoiseData()
: GenerateWaveformData(); // 波形数据
// 计算错误数据
var spikeData = GenerateSpikeData(); // 错误尖峰数据
// 合成最终结果
for (int i = 0; i < config.samples; i++)
{
// 正常线条加错误尖峰
var lineValue = waveformData[i] + spikeData[i];
_displayData[i] = Mathf.Clamp(lineValue, -_screenHeight / 1.8f, _screenHeight / 1.8f);
}
}
private float[] GenerateNoiseData()
{
for (var i = 0; i < config.samples; i++)
{
_noiseData[i] = Random.Range(-config.AdjustedNoiseAmplitude, config.AdjustedNoiseAmplitude);
}
return _noiseData;
}
private float[] GenerateWaveformData()
{
// 计算波形
for (int i = 0; i < config.samples; i++)
{
float basicAngle = TransIndexToAngle(i);
// 正波形
float wavePoint = CalcLineValueByAngle(basicAngle);
// 如果有相位偏移
if (_waveParam.PhaseShift > 0)
{
wavePoint -= CalcLineValueByAngle(basicAngle + _waveParam.PhaseShift);
}
_waveformData[i] = wavePoint;
}
return _waveformData;
}
private float TransIndexToAngle(int idx)
{
return (float)idx / config.samples * Mathf.PI * 2f * config.spatialFrequency;
}
private float CalcLineValueByAngle(float angle)
{
// 计算波形值
var value = _waveParam.waveformType switch
{
WaveformType.Sine => Mathf.Sin(angle),
WaveformType.Square => Mathf.Sign(Mathf.Sin(angle)),
WaveformType.Triangle => Mathf.PingPong(angle / Mathf.PI, 1f) * 2f - 1f,
WaveformType.Sawtooth => angle / (Mathf.PI * 2f) % 1f * 2f - 1f, // 统一周期为 2π
WaveformType.Pulse => Mathf.PingPong(angle / (Mathf.PI * 2f), 1f) > 0.5f ? 1f : -1f, // 统一周期为 2π
WaveformType.HotNoise => Mathf.PerlinNoise(angle / (Mathf.PI * 2f), Time.time * config.temporalFrequency) *
2f - 1f, // 统一周期为 2π
WaveformType.Composite => Mathf.Sin(angle) + Mathf.Sin(angle * 2f) * 0.5f, // 组合波形也统一周期
_ => 0f
};
return value * config.amplitude; // 最终返回角度对应波形
}
private float[] GenerateSpikeData()
{
if (CheckErrorShow())
{
// 错误点可以显示
for (var i = 0; i < config.samples; i++)
{
_spikeData[i] = CalcSpikeValueByIdx(i);
}
}
else
{
// 错误点尚不可显示,全部置为0
Array.Clear(_spikeData, 0, _spikeData.Length);
}
return _spikeData;
}
/// <summary>
/// 错误点是否已经可以定位
/// </summary>
/// <returns></returns>
private bool CheckErrorShow()
{
// 没有错误当然就不显示尖峰点
if (_waveParam.spikeIdx == -1) return false;
// 尖峰点小于1/3时显示
return _displayData[_waveParam.spikeIdx] < config.amplitude / 3f;
}
private float CalcSpikeValueByIdx(int idx)
{
var distanceToSpike = Mathf.Abs(_waveParam.spikeIdx - idx);
// 尖峰范围外直接数据为0
if (distanceToSpike > config.spikeRange) return 0f;
// 尖峰范围内计算
// 计算随机范围
var attenuation = (1f - distanceToSpike / 20f) * 0.9f;
var baseAmplitude = _displayData[idx] * config.AmplitudeScale * (_screenHeight / 2f);
var randomRange = (Mathf.Abs(baseAmplitude) - config.amplitude / 3f) * attenuation;
// 得出结果
if (Mathf.Abs(baseAmplitude) < config.amplitude / 3)
{
return Random.Range(-randomRange, randomRange); // 对 Y 值进行随机扰动
}
return 0;
}
#endregion
/// <summary>
/// 根据计算出的点数绘制线条
/// </summary>
private void DrawLine()
{
for (int i = 0; i < config.samples; i++)
{
// 计算线上每个点的位置
float x = (float)i / (config.samples - 1) * _screenWidth - _screenWidth / 2f; // x轴数据
float y = _displayData[i] * config.AmplitudeScale * (_screenHeight / 2f);
Vector3 position = new Vector3(x, y, 0);
// 设置
_waveformLine.SetPosition(i, position);
}
// 设置颜色和宽度
_waveformLine.startColor = config.LineColor;
_waveformLine.endColor = config.LineColor;
_waveformLine.startWidth = config.LineWidth;
_waveformLine.endWidth = config.LineWidth;
}
private int GeneSpikeIdx()
{
float[] waveData = GenerateWaveformData();
int resIdx = GetRandomIndexFromTop20Percent(waveData);
Array.Clear(waveData, 0, waveData.Length);
return resIdx;
}
int GetRandomIndexFromTop20Percent(float[] array)
{
int n = array.Length;
int topCount = (int)Math.Ceiling(n * 0.2);
var indexedArray = array
.Select((value, index) => new { Value = Math.Abs(value), Index = index })
.OrderByDescending(item => item.Value)
.Take(topCount)
.Select(item => item.Index)
.ToArray();
int randomIndex = indexedArray[Random.Range(0, indexedArray.Length)];
return randomIndex;
}
#region 对外暴露
/// <summary>
/// 设置屏幕尺寸作为参数
/// </summary>
/// <param name="width">宽</param>
/// <param name="height">高</param>
public void SetLineSize(float width, float height)
{
_screenHeight = height;
_screenWidth = width;
}
/// <summary>
/// 显示波形
/// </summary>
public void PlayWave(WaveformType waveformType, float initialPhase, bool hasError)
{
// 配置波形数据
config.waveformType = waveformType;
_waveParam = new WaveParam
{
waveformType = waveformType,
spikeIdx = -1,
initPhaseShift = initialPhase,
varPhaseShift = 0
};
_waveParam.spikeIdx = hasError ? GeneSpikeIdx() : -1;
// 进行播放
_waveformLine.enabled = true;
_isWavePlaying = true;
}
/// <summary>
/// 停止显示波形
/// </summary>
public void StopWave()
{
// 清除数据
config.waveformType = WaveformType.Default;
_waveParam = new WaveParam
{
waveformType = WaveformType.Default,
spikeIdx = -1,
initPhaseShift = 0,
varPhaseShift = 0
};
// 停止
_isWavePlaying = false;
_waveformLine.enabled = false;
}
/// <summary>
/// 获取尖峰点坐标
/// </summary>
/// <returns></returns>
public float GetSpikeX()
{
if (CheckErrorShow())
{
return _waveParam.spikeIdx / (float)config.samples * _screenWidth - _screenWidth / 2;
}
return float.NegativeInfinity;
}
/// <summary>
/// 调整相位
/// </summary>
/// <param name="phaseShift">相位</param>
public void AdjustPhaseShift(float phaseShift)
{
_waveParam.varPhaseShift = phaseShift * 2 * Mathf.PI;
}
#endregion
}
[Serializable]
public class WaveformConfig
{
public int samples = 1024;
public float amplitude = 0.6f;
public int spikeRange = 20;
public float spatialFrequency = 1f;
public float temporalFrequency = 0.1f;
public float targetWaveformNoiseAmplitude = 0.1f;
public float noiseAmplitude = 0.5f;
public Color noiseColor = Color.gray;
public Color clearColor = Color.green;
public Color hotColor = Color.red;
public WaveformType waveformType;
public Color LineColor
{
get
{
if (waveformType == WaveformType.Default)
{
return noiseColor;
}
return waveformType == WaveformType.HotNoise ? hotColor : clearColor;
}
}
public float LineWidth => waveformType != WaveformType.Default ? 0.05f : 0.02f;
public float AmplitudeScale => waveformType != WaveformType.Default ? 1f : 0.2f;
public float AdjustedNoiseAmplitude =>
waveformType != WaveformType.Default ? targetWaveformNoiseAmplitude : noiseAmplitude;
}
public struct WaveParam
{
public WaveformType waveformType;
public float initPhaseShift;
public int spikeIdx;
public float varPhaseShift;
public float PhaseShift => initPhaseShift + varPhaseShift;
}
}