using System.Diagnostics; using System.Windows.Threading; namespace Yw.WpfUI.Hydro { internal class LogicalFlowEffect3D : ModelVisual3D { public LogicalFlowEffect3D(LogicalLink3D link, LogicalMaterialHelper materialHelper) { _link = link; _materialHelper = materialHelper; this.Content = new Model3DGroup(); InitializeParticles(); _animationTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(16) }; } private readonly LogicalMaterialHelper _materialHelper = null; private const int _max_particles = 500;//最大粒子数 private readonly LogicalLink3D _link;//管段 private readonly Random _random = new();//随机 private readonly List _particles = new(_max_particles);//粒子 private readonly DispatcherTimer _animationTimer;//动画定时器 private readonly Stopwatch _stopWatch = new();//秒表 /// /// 流速 /// public double FlowRate { get; set; } = 1.0; /// /// 湍流 /// public double Turbulence { get; set; } = 0.3; /// /// 是否运行 /// public bool IsRunning { get; private set; } #region 基础方法 //计算最优粒子数量 private int CalculateOptimalParticleCount() { var diameter = Yw.Settings.HydroL3dParasHelper.HydroL3d.Logical.Link.Normal.Diameter; var length = (_link.EndPosition - _link.StartPosition).Length; double volume = Math.PI * Math.Pow(diameter / 2, 2) * length; int count = (int)(volume * 0.3); return Math.Clamp(count, 20, _max_particles); } //计算粒子尺寸 private double CalculateParticleSize() { var diameter = Yw.Settings.HydroL3dParasHelper.HydroL3d.Logical.Link.Normal.Diameter; var length = (_link.EndPosition - _link.StartPosition).Length; return diameter * (_random.NextDouble() * 0.3 + 0.2); } //应用湍流 private Point3D ApplyTurbulence(Point3D position, Vector3D velocity) { double diameter = Yw.Settings.HydroL3dParasHelper.HydroL3d.Logical.Link.Normal.Diameter; var normal1 = Vector3D.CrossProduct(velocity, new Vector3D(0, 0, 1)); var normal2 = Vector3D.CrossProduct(velocity, normal1); double turbulence = Turbulence * diameter; //position += normal1 * (_random.NextDouble() - 0.5) * turbulence; //position += normal2 * (_random.NextDouble() - 0.5) * turbulence; return position; } //获取沿着管段的点 private Point3D GetPointAlongLink(double t) { return new Point3D( _link.EndPosition.X + t * (_link.EndPosition.X - _link.StartPosition.X), _link.StartPosition.Y + t * (_link.EndPosition.Y - _link.StartPosition.Y), _link.StartPosition.Z + t * (_link.EndPosition.Z - _link.StartPosition.Z)); } //获取基础速度 private Vector3D GetBaseVelocity() { var direction = _link.EndPosition - _link.StartPosition; direction.Normalize(); return direction; } //是否管段外部 private bool IsOutsideLink(Point3D point) { var linkVec = _link.EndPosition - _link.StartPosition; var pointVec = point - _link.StartPosition; double dot = Vector3D.DotProduct(linkVec, pointVec); if (dot < 0 || dot > linkVec.LengthSquared) { return true; } double diameter = Yw.Settings.HydroL3dParasHelper.HydroL3d.Logical.Link.Normal.Diameter; double distance = (pointVec - linkVec * (dot / linkVec.LengthSquared)).Length; return distance > diameter * 0.6; } //重置粒子 private void ResetParticle(LogicalParticle3D particle) { double t = _random.NextDouble(); particle.Position = GetPointAlongLink(t); particle.Velocity = GetBaseVelocity() * (_random.NextDouble() * 0.4 + 0.8); particle.Age = 0; var position = particle.Position; particle.Position = ApplyTurbulence(position, particle.Velocity); particle.Update(); } #endregion //初始化粒子 private void InitializeParticles() { var modelGroup = (Model3DGroup)this.Content; modelGroup.Children.Clear(); int particleCount = CalculateOptimalParticleCount(); for (int i = 0; i < particleCount; i++) { var particle = new LogicalParticle3D(_materialHelper); particle.Position = _link.StartPosition; particle.Size = CalculateParticleSize(); particle.Color = Colors.Green; particle.Lifetime = _random.NextDouble() * 10 + 5; particle.Age = _random.NextDouble() * 10; ResetParticle(particle); _particles.Add(particle); modelGroup.Children.Add(particle.Content); } } /// /// 开始动画 /// public void Play() { if (IsRunning) { return; } _animationTimer.Tick += OnAnimationFrame; _animationTimer.Start(); _stopWatch.Start(); this.IsRunning = true; } /// /// 停止动画 /// public void Stop() { if (!IsRunning) { return; } _animationTimer.Tick -= OnAnimationFrame; _animationTimer.Stop(); _stopWatch.Stop(); this.IsRunning = false; } private void OnAnimationFrame(object sender, EventArgs e) { double deltaTime = _stopWatch.Elapsed.TotalSeconds; _stopWatch.Restart(); double speedFactor = this.FlowRate * deltaTime * 2; foreach (var particle in _particles) { particle.Age += deltaTime; if (particle.Age > particle.Lifetime) { ResetParticle(particle); continue; } particle.Position += particle.Velocity * speedFactor; if (IsOutsideLink(particle.Position)) { ResetParticle(particle); continue; } if (_random.NextDouble() < 0.1) { particle.Position = ApplyTurbulence(particle.Position, particle.Velocity); } particle.Update(); } } } }