65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 波浪模拟器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (62投票s)

2015 年 5 月 24 日

CPOL

9分钟阅读

viewsIcon

49484

downloadIcon

3228

使用 VS 2010 C# 制作的实验性波浪模拟程序。

引言

这是一个实验性的波浪模拟程序。它用于在粒子池中产生波浪。例如,可以生成水波。可以实现障碍物。可以模拟波的反射、衍射和相互作用。

背景

我想用我自己的想法在计算机环境中生成波浪。由于您可以对波浪进行深入研究,我将不深入探讨物理学。我只会解释我自己的波浪生成技术。以下是基本概念。

1 - 方形水池

这是波浪生成的地方。它充满了粒子。上面的绿色-蓝色方形表面就是它。

2 - 粒子

粒子类似于像素。它们有序排列,构成整个水池。每个粒子都有高度、速度、加速度、质量和可持续性(抗阻尼)。它们只上下移动。每个粒子在高度上都受到周围 8 个相邻粒子的吸引。在上图的插图中,红色粒子是主粒子,蓝色粒子是相邻粒子。这种吸引力是粒子自然移动的唯一原因。

3 - 振荡器

它用于生成正弦波。它会主动地使某个粒子上下移动,从而吸引相邻粒子,使它们也上下移动。这就形成了一个传播的波。在上图的第一个图像中,底部靠近水平中心的两个振荡器正在运行。

4 - 障碍物

它是一个静态粒子。它不移动。不受任何力影响。它不会吸引其他粒子。在上图的第一个图像中,底部的黄色线条是障碍物。

它是如何工作的?

波浪引擎不使用任何复杂的算法。它仅仅使用以下方法。

Force = Mass * Acceleration;

所有粒子相互吸引。它们只是想在高度上保持接近。请记住,它们只改变高度。它们想处于相同的高度。吸引力与粒子之间的高度差成正比。

当引擎进行计算时,它会检查每一个粒子。如果一个粒子是振荡器或静态粒子,引擎就不会对该粒子进行力计算,然后继续处理下一个。如果粒子是正常的动态粒子,则会进行计算。它从加速度开始。

Acceleration = (H - h) / m;

H 是周围八个(相邻)粒子的平均高度。

h 是被包围粒子的当前高度。

m 是质量。

另一个类似的公式如下。

Acceleration = Math.Sign(H - h) * Math.Pow(Math.Abs(H - h), P) / m;

P 是功率。

现在,它由于高度差而获得了加速度。还有一种阻力。

该公式抵消了加速度。它在某种程度上模拟了阻尼,并阻止了进一步的波浪传播。

Acceleration -= Velocity / Sustainability;

之后,引擎会将加速度限制在一个预设值。因此,绝对加速度不能超过该限制。Limit 对于消除不确定性和溢出是必要的。

if (Acceleration > Limit)
  Acceleration = Limit;
else if (Acceleration < -Limit)
  Acceleration = -Limit;

现在加速度会影响速度。

Velocity += Acceleration;

速度也影响高度,但不是一次性全部影响。

Height += Velocity / ActionResolution;

限制高度值。

if (Height > Limit)
  Height = Limit;
else if (Height < -Limit)
  Height = -Limit;

还有一个叫做 ActionResolution 的值,用于防止正反馈。理解这一点很重要,因为如果粒子同时进行所有移动,混乱是不可避免的。那么问题是什么?

1 - 想象两个想要达到相同高度但处于不同高度的粒子。

d 是粒子到原点的距离,因此这两个粒子之间的距离为 2d。这种情况会发生什么?它们首先获得很大的加速度。由于阻尼,加速度会降低。加速度影响速度。现在该影响高度了。如果我们同时移动这些粒子,下一刻很可能会变成这样。

它们之间的距离反而变大了,而不是变小了。它们永远不会停止。如果我们不让它们同时移动,它们就会停止。它们必须一步一步地前进,每一步都必须缩短距离。这将防止上述情况发生,最终它们会在相同的高度停止。

在最后一步,所有粒子作为一个整体移动到原点。否则,它们可能会远离原点,因为没有力将它们拉向原点。要做到这一点,将所有粒子的高度值相加,然后将结果除以粒子数量,从而得出粒子的平均高度。我们从每个粒子的身高中减去这个值。

int Total_Height = 0;

for (int index = 0; index < Height_Array.Length; index++)
Total_Height += Height_Array[index];

for (int index = 0; index < Height_Array.Length; index++)
Height_Array[index] -= Total_Height / Height_Array.Length;

计算至此结束。最后,波浪引擎描绘粒子并在控件上进行渲染。

使用代码

有一个名为 WaveEngine.cs 的类来完成所有计算和渲染。首先将其实现到您的项目中。

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows;
using System.Windows.Forms;

namespace WaveSimulator
{
    public class WaveEngine
    {
        Bitmap bmp; // This will be our canvas.
        BitmapData bd; // This will be used to modify the RGB pixel array of the "bmp".

        // "vd" means "vertex data"
        float[] vd; // Height map
        float[] vdv; // Velocity map
        float[] vda; // Acceleration map
        float[] vds; // Sustainability map. Sustainability can be thought as anti-damping.
        bool[] vd_static; // Static particle map. Particles which will act like a obstacle or wall.


        float mass = 0.1f; // Mass of each particle. It is the same for all particles.
        float limit = 500f; // Maximum absolute height a particle can reach.
        float action_resolution = 20f; // Resolution of movement of particles.
        float sustain = 1000f; // Anti-damping. Propagation range increases by increasing this variable. Minimum is 1f.
        int delay = 1; // Time-out in milliseconds for force calculations.
        float phase1 = 0f; // Current phase value of oscillator1.
        float phase2 = 0f; // Current phase value of oscillator2.
        float freq1 = 0.2f; // Phase changing rate of oscillator1 per calculation. Frequency increases by increasing this variable.
        float freq2 = 0.2f; // Phase changing rate of oscillator2 per calculation. Frequency increases by increasing this variable.
        float power = 1.0f; // Power of the output force exerted on each particle. Natural value is 1.0f

        BufferedGraphics bufgraph; // Double-buffered graphics for rendering. It minimizes flickering.
        BufferedGraphicsContext bufgcont; // Will be used to initialize bufgraph.

        Thread ForceCalcT; // Worker thread that will do force calculations.

        Mutex mutex; // This will limit the access to variables.

        bool work_now = false; // True = Thread must make calculations now, False = Thread must sleep now.

        bool highcont = false; // High contrast drawing.

        bool disposing = false; // It will be true once the termination starts.

        bool osc1active = false; // Is oscillator1 is turned on?
        bool osc2active = false; // Is oscillator2 is turned on?

        int osc1point = 0; // Location of the oscillator1 in the wave pool. It is an index value.
        int osc2point = 0; // Location of the oscillator2 in the wave pool. It is an index value.

        int size = 200; // Size of the wave pool. It indicates both the width and height since the pool will always be a square.

        Color color1 = Color.Black; // Color of the crest or trough.
        Color color2 = Color.Cyan; // Color of the crest or trough.

        Color colorstatic = Color.Yellow; // Color of the static particles.

        // These variables are used for edge absorbtion. It is used for eliminating reflection from window boundaries.
        int absorb_offset = 10; // Offset from each window boundary where the sustainability starts to decrease.
        float min_sustain = 2f; // The lowest sustainability value. They are located at the boundaries.
        bool edge_absorbtion = true; // If true, the particles near the boundaries will have low sustainability.


        Control control; // This will be the control where the engine runs and renders on.

        public enum ParticleAttribute
        {
            Height = 1,
            Velocity = 2,
            Acceleration = 4,
            Sustainability = 8,
            Fixity = 16,
            All = 32,
        }

        public float Mass
        {
            get { return mass; }
            set
            {
                if (value > 0f)
                {
                    mutex.WaitOne();
                    mass = value;
                    mutex.ReleaseMutex();
                }
            }
        }

        public float Limit
        {
            get { return limit; }
            set
            {
                if (value > 0f)
                {
                    mutex.WaitOne();
                    limit = value;
                    mutex.ReleaseMutex();
                }
            }
        }

        public float ActionResolution
        {
            get { return action_resolution; }
            set
            {
                if (value >= 1f)
                {
                    mutex.WaitOne();
                    action_resolution = value;
                    mutex.ReleaseMutex();
                }
            }
        }

        public float Sustainability
        {
            get { return sustain; }
            set
            {
                if (value >= 1f)
                {
                    mutex.WaitOne();
                    sustain = value;
                    setSustain();
                    mutex.ReleaseMutex();
                }
            }
        }
        public int Delay
        {
            get { return delay; }
            set
            {
                if (value >= 0)
                {
                    mutex.WaitOne();
                    delay = value;
                    mutex.ReleaseMutex();
                }
            }
        }
        public float PhaseRate1
        {
            get { return freq1; }
            set
            {
                if (value > 0f && value < Math.PI * 2f)
                {
                    mutex.WaitOne();
                    freq1 = value;
                    mutex.ReleaseMutex();
                }
            }
        }
        public float PhaseRate2
        {
            get { return freq2; }
            set
            {
                if (value > 0f && value < Math.PI * 2f)
                {
                    mutex.WaitOne();
                    freq2 = value;
                    mutex.ReleaseMutex();
                }
            }
        }
        public float Power
        {
            get { return power; }
            set
            {
                if (power > 0f)
                {
                    mutex.WaitOne();
                    power = value;
                    mutex.ReleaseMutex();
                }
            }
        }
        public int Size
        {
            get { return size; }
            set
            {
                if (size >= 1f)
                {
                    mutex.WaitOne();
                    size = value;
                    setPool();
                    mutex.ReleaseMutex();
                }
            }
        }
        public float EdgeSustainability
        {
            get { return min_sustain; }
            set
            {
                if (value >= 1f)
                {
                    mutex.WaitOne();
                    min_sustain = value;
                    setSustain();
                    mutex.ReleaseMutex();
                }
            }
        }
        public int AbsorbtionOffset
        {
            get { return absorb_offset; }
            set
            {
                if (value > 0 && value < size / 2)
                {
                    mutex.WaitOne();
                    absorb_offset = value;
                    setSustain();
                    mutex.ReleaseMutex();
                }
            }
        }
        public Color Color1
        {
            get { return color1; }
            set
            {
                mutex.WaitOne();
                color1 = value;
                mutex.ReleaseMutex();
            }
        }
        public Color Color2
        {
            get { return color2; }
            set
            {
                mutex.WaitOne();
                color2 = value;
                mutex.ReleaseMutex();
            }
        }
        public Color ColorStatic
        {
            get { return colorstatic; }
            set
            {
                mutex.WaitOne();
                colorstatic = value;
                mutex.ReleaseMutex();
            }
        }
        public bool HighContrast
        {
            get { return highcont; }
            set
            {
                mutex.WaitOne();
                highcont = value;
                mutex.ReleaseMutex();
            }
        }
        public bool EdgeAbsorbtion
        {
            get { return edge_absorbtion; }
            set
            {
                mutex.WaitOne();
                edge_absorbtion = value;
                setSustain();
                mutex.ReleaseMutex();
            }
        }

        public bool Oscillator1Active
        {
            get { return osc1active; }
            set
            {
                mutex.WaitOne();
                osc1active = value;
                setSustain();
                mutex.ReleaseMutex();
            }
        }

        public bool Oscillator2Active
        {
            get { return osc2active; }
            set
            {
                mutex.WaitOne();
                osc2active = value;
                setSustain();
                mutex.ReleaseMutex();
            }
        }

        public Point Oscillator1Position
        {
            get { return new Point(osc1point % size, (int)Math.Floor((float)osc1point / (float)size)); }
            set
            {
                if (value.X + value.Y * size < size * size)
                {
                    mutex.WaitOne();
                    osc1point = value.X + value.Y * size;
                    setSustain();
                    mutex.ReleaseMutex();
                }
            }
        }

        public Point Oscillator2Position
        {
            get { return new Point(osc2point % size, (int)Math.Floor((float)osc2point / (float)size)); }
            set
            {
                if (value.X + value.Y * size < size * size)
                {
                    mutex.WaitOne();
                    osc2point = value.X + value.Y * size;
                    setSustain();
                    mutex.ReleaseMutex();
                }
            }
        }

        /// <summary>
        /// Initializes the WaveEngine
        /// </summary>
        /// <param name="control">The control where the engine renders on.</param>
        public WaveEngine(Control control)
        {
            this.control = control;
            control.Resize += new EventHandler(control_Resize);
            setPool();
            mutex = new Mutex();
            ForceCalcT = new Thread(() =>
            {

                while (!disposing)
                {
                    try
                    {
                        while (work_now)
                        {
                            mutex.WaitOne();
                            int beginning = System.Environment.TickCount;
                            while (System.Environment.TickCount - beginning < delay)
                                CalculateForces();
                            generatebitmap();
                            bufgraph.Graphics.DrawImage(bmp, 0, 0, control.ClientSize.Width, control.ClientSize.Height);
                            bufgraph.Render();
                            mutex.ReleaseMutex();
                            Thread.Sleep(delay);
                        }
                    }
                    catch
                    { work_now = false; mutex.ReleaseMutex(); }
                    Thread.Sleep(0);
                }

            });

            ForceCalcT.Start();

        }

        void control_Resize(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem((Object arg1) =>
            {
                mutex.WaitOne();
                if (bufgraph != null)
                    bufgraph.Dispose();

                if (bufgcont != null)
                    bufgcont.Dispose();

                bufgcont = new BufferedGraphicsContext();

                bufgraph = bufgcont.Allocate(control.CreateGraphics(), control.ClientRectangle);
                mutex.ReleaseMutex();
            });

        }


        /// <summary>
        /// Sets particles' specified attribute(s) to a specified value in a specified rectangular area.
        /// </summary>
        /// <param name="rect">Rectangular area which contains particles.</param>
        /// <param name="value">Value to set the particles to.</param>
        /// <param name="partatt">Attribute(s) that will be set.</param>
        public void SetParticles(Rectangle rect, float value, ParticleAttribute partatt)
        {
            mutex.WaitOne();

            if (rect.X < 0)
                rect.X = 0;

            if (rect.Y < 0)
                rect.Y = 0;

            if (rect.Width + rect.X > size)
                rect.Width -= (rect.X + rect.Width) - size;

            if (rect.Height + rect.Y > size)
                rect.Height -= (rect.Y + rect.Height) - size;

            bool xh = false, xv = false, xa = false, xs = false, xf = false;
            // Let's see which attributes we are gonna deal with.
            if ((ParticleAttribute.All & partatt) == ParticleAttribute.All)
            {
                xh = true; xv = true; xa = true; xs = true; xf = true;
            }
            else
            {
                if ((ParticleAttribute.Height & partatt) == ParticleAttribute.Height)
                    xh = true;
                if ((ParticleAttribute.Velocity & partatt) == ParticleAttribute.Velocity)
                    xv = true;
                if ((ParticleAttribute.Acceleration & partatt) == ParticleAttribute.Acceleration)
                    xa = true;
                if ((ParticleAttribute.Sustainability & partatt) == ParticleAttribute.Sustainability)
                    xs = true;
                if ((ParticleAttribute.Fixity & partatt) == ParticleAttribute.Fixity)
                    xf = true;
            }

            for (int y = rect.Y * size; y < rect.Y * size + rect.Height * size; y += size)
            {
                for (int x = rect.X; x < rect.X + rect.Width; x++)
                {
                    if (xh)
                        vd[x + y] = value;
                    if (xv)
                        vdv[x + y] = value;
                    if (xa)
                        vda[x + y] = value;
                    if (xs)
                        vds[x + y] = value;
                    if (xf)
                        vd_static[x + y] = Convert.ToBoolean(value);
                }
            }
            mutex.ReleaseMutex();
        }

        /// <summary>
        /// Gives a float array of specified attribute of particles in a specified rectangular area.
        /// </summary>
        /// <param name="rect">Rectangular area which contains particles.</param>
        /// <param name="partatt">Attribute whose array will be given. Only one attribute can be specified and "All" cannot be specified.</param>
        public float[] GetParticles(Rectangle rect, ParticleAttribute partatt)
        {
            float[] result = new float[1];

            bool xh = false, xv = false, xa = false, xs = false, xf = false;

            if ((int)partatt == 1 || (int)partatt == 2 || (int)partatt == 4 || (int)partatt == 8 || (int)partatt == 16)
            {
                mutex.WaitOne();

                if (rect.X < 0)
                    rect.X = 0;

                if (rect.Y < 0)
                    rect.Y = 0;

                if (rect.Width + rect.X > size)
                    rect.Width -= (rect.X + rect.Width) - size;

                if (rect.Height + rect.Y > size)
                    rect.Height -= (rect.Y + rect.Height) - size;

                result = new float[rect.Width * rect.Height];

                if (partatt == ParticleAttribute.Height)
                    xh = true;
                if (partatt == ParticleAttribute.Velocity)
                    xv = true;
                if (partatt == ParticleAttribute.Acceleration)
                    xa = true;
                if (partatt == ParticleAttribute.Sustainability)
                    xs = true;
                if (partatt == ParticleAttribute.Fixity)
                    xf = true;

                int r = 0;
                for (int y = rect.Y * size; y < rect.Y * size + rect.Height * size; y += size)
                {
                    for (int x = rect.X; x < rect.X + rect.Width; x++)
                    {
                        if (xh)
                            result[r] = vd[x + y];
                        if (xv)
                            result[r] = vdv[x + y];
                        if (xa)
                            result[r] = vda[x + y];
                        if (xs)
                            result[r] = vds[x + y];
                        if (xf)
                            result[r] = Convert.ToSingle(vd_static[x + y]);
                        r += 1;
                    }
                }
                mutex.ReleaseMutex();
            }

            return result;
        }
        /// <summary>
        /// Starts the force calculation.
        /// </summary>
        public void Run()
        {
            work_now = true;
        }

        /// <summary>
        /// Suspends the force calculation indefinitely.
        /// </summary>
        public void Stop()
        {
            work_now = false;
        }

        public void Dispose()
        {
            work_now = false;
            disposing = true;
            ThreadPool.QueueUserWorkItem((Object arg1) =>
            {
                mutex.WaitOne();
                bmp.Dispose();
                mutex.Close();
            });
        }

        void CalculateForces()
        {

            float total_height = 0;// This will be used to shift the height center of the whole particle system to the origin.

            // This loop calculates the forces exerted on the particles.
            for (int index = 0; index < vd.Length; index += 1)
            {
                // If this is a static particle, it will not move at all. Continue with the next particle.
                if (vd_static[index])
                {
                    vd[index] = 0;
                    vdv[index] = 0;
                    vda[index] = 0;
                    continue;
                }

                if (index == osc1point && osc1active)
                {
                    // This is where the oscillator1 is located. It is currently active.
                    // So this particle only serves as an oscillator for neighbor particles.
                    // It will not be affected by any forces. It will just move up and down.
                    vdv[index] = 0;
                    vda[index] = 0;
                    vd[index] = limit * (float)Math.Sin(phase1);
                    phase1 += freq1;
                    if (phase1 >= 2f * (float)Math.PI)
                        phase1 -= (float)Math.PI * 2f;

                    continue;
                }

                if (index == osc2point && osc2active)
                {
                    vdv[index] = 0;
                    vda[index] = 0;
                    vd[index] = limit * (float)Math.Sin(phase2);
                    phase2 += freq2;
                    if (phase2 >= 2f * (float)Math.PI)
                        phase2 -= (float)Math.PI * 2f;

                    continue;
                }

                // So this particle is neither an oscillator nor static. So let's calculate the force.

                // Reset the acceleration. We do this because acceleration dynamically changes with the force.
                vda[index] = 0;

                // Sum up all the height values so we will find the average height of the system.
                // This doesn't contribute to the force calculation. It is immaterial.
                total_height += vd[index];

                // Now we will find out the average height of the 8 neighbor particles.
                // So we will know where the current particle will be attracted to.

                // "heights" is the sum of all the height values of neighbor particles.
                float heights = 0;
                // "num_of_part" is the number of particles which contributed to the "heights".
                int num_of_part = 0;

                //// UP
                if (!(index >= 0 && index < size)) // Make sure that this point is not on a boundary.
                {
                    if (!vd_static[index - size]) // Make sure that the neighbor particle is not static.
                    {
                        heights += vd[index - size];

                        num_of_part += 1;
                    }
                }

                //// UPPER-RIGHT
                if (!((index + 1) % size == 0 || (index >= 0 && index < size)))
                {
                    if (!vd_static[index - size + 1])
                    {
                        heights += vd[index - size + 1];

                        num_of_part += 1;
                    }
                }

                //// RIGHT
                if (!((index + 1) % size == 0))
                {
                    if (!vd_static[index + 1])
                    {
                        heights += vd[index + 1];

                        num_of_part += 1;
                    }
                }

                //// LOWER-RIGHT
                if (!((index + 1) % size == 0 || (index >= (size * size) - size && index < (size * size))))
                {
                    if (!vd_static[index + size + 1])
                    {
                        heights += vd[index + size + 1];

                        num_of_part += 1;
                    }
                }

                //// DOWN
                if (!(index >= (size * size) - size && index < (size * size)))
                {
                    if (!vd_static[index + size])
                    {
                        heights += vd[index + size];

                        num_of_part += 1;
                    }
                }

                //// LOWER-LEFT
                if (!(index % size == 0 || (index >= (size * size) - size && index < (size * size))))
                {
                    if (!vd_static[index + size - 1])
                    {
                        heights += vd[index + size - 1];

                        num_of_part += 1;
                    }
                }

                //// LEFT
                if (!(index % size == 0))
                {
                    if (!vd_static[index - 1])
                    {
                        heights += vd[index - 1];

                        num_of_part += 1;
                    }
                }

                // UPPER-LEFT

                if (!(index % size == 0 || (index >= 0 && index < size)))
                {
                    if (!vd_static[index - size - 1])
                    {
                        heights += vd[index - size - 1];

                        num_of_part += 1;
                    }
                }

                if (num_of_part != 0)
                {
                    heights /= (float)num_of_part;

                    if (power != 1.0f)
                        vda[index] += Math.Sign(heights - vd[index]) * (float)Math.Pow(Math.Abs(vd[index] - heights), power) / mass;
                    else
                        vda[index] += -(vd[index] - heights) / mass;
                }

                // Damping takes place.
                vda[index] -= vdv[index] / vds[index];

                // Don't let things go beyond their limit.
                // This makes sense. It eliminates a critic uncertainty.
                if (vda[index] > limit)
                    vda[index] = limit;
                else if (vda[index] < -limit)
                    vda[index] = -limit;

            }
            // Now we have finished with the force calculation.

            // Origin height is zero. So "shifting" is the distance between the system average height and the origin.
            float shifting = -total_height / (float)vd.Length;


            // We are taking the final steps in this loop
            for (int index = 0; index < vd.Length; index += 1)
            {

                // Acceleration feeds velocity. Don't forget that we took care of the damping before.
                vdv[index] += vda[index];

                // Here is the purpose of "action_resolution":
                // It is used to divide movements.
                // If the particle goes along the road at once, a chaos is most likely unavoidable.
                if (vd[index] + vdv[index] / action_resolution > limit)
                    vd[index] = limit;
                else if (vd[index] + vdv[index] / action_resolution <= limit && vd[index] + vdv[index] / action_resolution >= -limit)
                    vd[index] += vdv[index] / action_resolution; // Velocity feeds height.
                else
                    vd[index] = -limit;

                // Here is the last step on shifting the whole system to the origin point.
                vd[index] += shifting;

            }

        }

        void generatebitmap()
        {
            if (bmp == null || bmp.Width != size)
                bmp = new Bitmap(size, size, PixelFormat.Format24bppRgb); // 24 bit RGB. No need for alpha channel.

            // Get the bitmap data of "bmp".
            bd = bmp.LockBits(new Rectangle(0, 0, size, size), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

            IntPtr ptr = bd.Scan0; // Get the address of the first line in "bd"
            int bytes = bd.Stride * bd.Height; // "Stride" gives the size of a line in bytes.
            byte[] rgbdata = new byte[bytes];


            // It's time for the coloration of the height.
            for (int index = 0; index < vd.Length; index++)
            {
                // Brightness. This value is the 'brightness' of the height.
                // Now we see why "limit" makes sense.
                byte bright = (byte)(((float)vd[index] + limit) / (float)((limit * 2f) / 255f));

                if (vd_static[index])
                {
                    rgbdata[index * 3] = ColorStatic.B;
                    rgbdata[index * 3 + 1] = ColorStatic.G;
                    rgbdata[index * 3 + 2] = ColorStatic.R;
                }
                else
                {

                    if (highcont)
                    {

                        byte red_average = (byte)((float)(color1.R + color2.R) / 2f);
                        byte green_average = (byte)((float)(color1.G + color2.G) / 2f);
                        byte blue_average = (byte)((float)(color1.B + color2.B) / 2f);

                        if (vd[index] > 0)
                        {
                            rgbdata[index * 3] = color1.B;
                            rgbdata[index * 3 + 1] = color1.G;
                            rgbdata[index * 3 + 2] = color1.R;
                        }

                        else if (vd[index] < 0)
                        {
                            rgbdata[index * 3] = color2.B;
                            rgbdata[index * 3 + 1] = color2.G;
                            rgbdata[index * 3 + 2] = color2.R;
                        }
                        else if (vd[index] == 0)
                        {
                            rgbdata[index * 3] = blue_average;
                            rgbdata[index * 3 + 1] = green_average;
                            rgbdata[index * 3 + 2] = red_average;
                        }
                    }
                    else
                    {
                        float brightr1 = (float)bright / 255f;
                        float brightr2 = 1f - (float)bright / 255f;
                        rgbdata[index * 3] = (byte)((float)color1.B * brightr1 + (float)color2.B * brightr2);
                        rgbdata[index * 3 + 1] = (byte)((float)color1.G * brightr1 + (float)color2.G * brightr2);
                        rgbdata[index * 3 + 2] = (byte)((float)color1.R * brightr1 + (float)color2.R * brightr2);
                    }

                }
            }

            // At last, we overwrite and release the bitmap data.
            System.Runtime.InteropServices.Marshal.Copy(rgbdata, 0, ptr, bytes);
            bmp.UnlockBits(bd);
        }

        /// <summary>
        /// Sets sustainability of each particle.
        /// </summary>
        void setSustain()
        {
            if (edge_absorbtion)
            {
                // We will fill "vds" array with "sustain" then we will deal with elements near to window boundaries.

                // Since we want the sustainability to decrease towards the edges, "min_sustain" can't be bigger than "sustain".
                if (min_sustain > sustain)
                {
                    min_sustain = 1.0f; // even "sustain" can't be less than 1.0f so this is a reliable value.
                }

                // Sustainability reduction fields should not mix with each other. So the maximum offset is the middle-screen.
                if (absorb_offset >= size / 2)
                {
                    absorb_offset = size / 2 - 1;
                }

                // This value is sustainability decreasion rate per row/column. The decreasion is linear.
                float dec = (sustain - min_sustain) / (float)absorb_offset;
                // This one stores the current sustainability.
                float cur = min_sustain;

                // First, we fill "vds" array with "sustain".
                for (int i = 0; i < vds.Length - 1; i++)
                    vds[i] = sustain;

                // This loop sets up the sustainability values for the top.
                for (int off = 0; off <= absorb_offset; off++)
                {
                    // Process each row/column from the edge to the offset.
                    for (int x = off; x < size - off; x++)
                    {
                        // Process each sustainability element in the current row/column
                        vds[x + off * size] = cur;
                    }
                    cur += dec;
                }

                cur = sustain; // Reset the current sustainability.

                // This loop sets up the sustainability values for the bottom.
                for (int off = 0; off <= absorb_offset; off++)
                {
                    for (int x = absorb_offset - off; x < size - (absorb_offset - off); x++)
                    {
                        vds[x + off * size + size * (size - absorb_offset - 1)] = cur;
                    }
                    cur -= dec;
                }

                cur = sustain;

                // This loop sets up the sustainability values for the left.
                for (int off = 0; off <= absorb_offset; off++)
                {
                    for (int x = absorb_offset - off; x < size - (absorb_offset - off); x++)
                    {
                        vds[x * size + (absorb_offset - off)] = cur;
                    }
                    cur -= dec;
                }

                cur = sustain;

                // This loop sets up the sustainability values for the right.
                for (int off = 0; off <= absorb_offset; off++)
                {
                    for (int x = absorb_offset - off; x < size - (absorb_offset - off); x++)
                    {
                        vds[x * size + off + size - absorb_offset - 1] = cur;
                    }
                    cur -= dec;
                }
            }
            else
            {
                // The only thing to do is to fill "vds" array with "sustain" in this case.
                for (int i = 0; i < vds.Length; i++)
                    vds[i] = sustain;
            }
        }

        /// <summary>
        /// Initializes the wave pool system.
        /// </summary>
        void setPool()
        {

            if (bufgraph != null)
                bufgraph.Dispose();

            if (bufgcont != null)
                bufgcont.Dispose();

            bufgcont = new BufferedGraphicsContext();

            bufgraph = bufgcont.Allocate(control.CreateGraphics(), control.ClientRectangle);

            vd = new float[size * size];

            vdv = new float[size * size];

            vda = new float[size * size];

            vd_static = new bool[size * size];

            vds = new float[size * size];

            setSustain();

        }


    }
}

 

现在,我们可以在 Form 中使用它。让我们先从这个开始。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WaveSimulator
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }
    }
}

在全局范围内声明该类的实例。

WaveEngine we;

在初始化期间初始化 we。在参数中指定 this

we = new WaveEngine(this);

现在我们可以在任何想要运行的地方运行它。让我们在初始化后立即运行它。

we.Run();

我们还需要在程序终止时处理 we。否则,程序将继续运行。以下代码对我来说效果很好。

private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    we.Dispose();
}

就这样。执行程序。您会看到一个类似这样的窗口。

这是最终的代码。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WaveSimulator
{
    public partial class MainForm : Form
    {
        WaveEngine we;

        public MainForm()
        {
            InitializeComponent();

            we = new WaveEngine(this);
            we.Run();
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            we.Dispose();
        }
    }
}

目前没有任何动作。我们需要更改粒子的一个属性才能看到一些动作。我们可以通过激活一个振荡器来实现。最多可以同时运行两个振荡器。

我们可以单击窗口在鼠标位置激活一个振荡器。这个效果会很好。

private void MainForm_MouseDown(object sender, MouseEventArgs e)
{

we.Oscillator1Position = new Point((int)(((float)e.X / (float)ClientSize.Width) * (float)we.Size), (int)(((float)e.Y / (float)ClientSize.Height) * (float)we.Size));

we.Oscillator1Active = true;
              
}

执行程序并在窗口中的任何位置单击。您将看到波浪。为了使其更有趣,请在初始化 we 后添加以下命令。

we.EdgeAbsorbtion = false;

现在波浪应该会从窗口边界反弹。

由于该类及其注释相当直观,我们将不逐一介绍。您可以下载源代码或演示。该项目完全使用了该类。除了主窗口,它还包含以下内容。

一个 Adjustments 窗口,用于控制所有属性。还有一个 1DView 窗口,用于查看水池的任何横截面区域。它提供了水池的侧视图。

关注点

当我开始制作这个项目时,我认为它可能不起作用,或者最多只能勉强工作,但当我意识到它的工作方式如此简单时,我感到很惊讶。我曾考虑过用弹簧将每个粒子拉向原点,但那失败了。总有些地方出错。大多数时候,我遇到一个令人讨厌的场景,如下所示。我称之为“混乱”,因为它确实感觉如此。

这种情况的原因是低运动分辨率导致的积极反馈,但这花了我很长时间才意识到。两个粒子首先互相吸引,获得很大的加速度,并以非常剧烈的方式移动。最终它们之间的距离比以前更大。在这种情况下,它们必须获得更大的加速度。因此,它们在每次移动中都在互相远离,而不是靠近。这导致了混乱。

我还遇到一个看似简单但却难以克服的问题。那就是吸收波浪以防止反射。在一个具有均匀可持续性的水池中,波浪会简单地从窗口边缘反弹。起初看到这一点很有趣,但很快我就想消除它。我试图让边缘附近的粒子静止,但这一点用都没有。我尝试了很多次,但都失败了。过了一段时间,我真的绞尽脑汁地思考:“什么可以吸收波浪?摩擦?当然不是……等等……等一下……为什么不是摩擦?边界上的高而光滑的摩擦……是的!为什么不试试呢?”它起作用了,但吸收参数高度依赖于粒子的属性。

还有一件令人讨厌的事情,我还没有弄清楚。当一个粒子吸引其他八个相邻粒子,并且该粒子本身也被它们吸引时,我们需要考虑每个粒子之间的距离。对吗?不幸的是,考虑距离会导致某些事情出错。

上面是一个说明图,显示了相邻粒子之间可能的两种距离。一种是 1 个单位,另一种是根号 2 个单位。根据距离,距离平方根为 2 的粒子之间的吸引力应该更小,而距离为 1 的粒子之间的吸引力应该更大,因此距离与吸引力成反比。考虑到这种情况会导致问题。我将合力除以距离,但这实际上并没有起作用。

历史

2014 年 5 月 27 日 - 重要更新

1 - 从属性和许多函数中删除了 QueueUserWorkItem 函数。它们以前使用线程是因为我不想这些操作暂停 UI,但这些多线程操作经常导致意外结果。

WaveEngine we = new WaveEngine();

we.Size = 100;

int x = we.Size;

x 最终是多少?它不是 100。如果此代码在没有任何中断的情况下执行,x 不会等于 100。这是因为属性设置是多线程的。在我们检索 Size 之前,线程无法完成操作。如果不想暂停 UI,则需要用户提供的线程。

2 - 删除了 SwitchStaticDynamic 函数。将改用 SetParticles 函数。

3 - 向 ParticleAttribute 枚举添加了“固定性”。这使得可以使用 SetParticles 函数在动态和静态粒子之间进行转换。

次要修复和改进。

重要提示: 当我在一台现代计算机上尝试我的程序时,我对其 CPU 性能感到震惊。我自己的电脑几乎有 10 年了,配备 1 GB RAM 和 2.01 GHz 双核 AMD CPU。我在一台配备 Intel i7 3.4 GHz 四核 CPU 的台式电脑上测试了我的程序。与我电脑上的模拟速度相比,这是一个极其快速的模拟。它非常快,但我希望通过平滑的 FPS 来减慢它的速度。我调整了 Delay,但这并没有给我一个平滑的 FPS。最后,我增加了 Size,因此粒子数量增加了。这增加了 CPU 的负载,最终渲染变得平滑而缓慢。Size 非常大,速度仍然令人满意。因此,如果您的 CPU 非常快和/或您认为模拟速度非常快,请增加 Size另一个选择是增加运动分辨率。

© . All rights reserved.