1900-01-02

2D流体ソース 2D流体ソース - Nao_uの日記 を含むブックマーク はてなブックマーク - 2D流体ソース - Nao_uの日記 2D流体ソース - Nao_uの日記 のブックマークコメント

namespace FluidTest
{
    //----------------------------------
    // 流体2Dセルクラス
    //----------------------------------
    class FluidCell2D
    {
        public int m_PosX;
        public int m_PosY;
        public float m_Pressure;        // 圧力
        public float m_PressurePlus;    // 圧力加算分
        public float[] m_Dencity;       // 密度
        public Vector2 m_Velocity;      // 流れ速度
        public Vector4[] m_Color;       // 色
        public bool m_bWall;            // 私は壁?

        public FluidCell2D m_Up;        // 上セルへの参照
        public FluidCell2D m_Right;     // 右セルへの参照
        public FluidCell2D m_Left;      // 左セルへの参照
        public FluidCell2D m_Down;      // 下セルへの参照

        public FluidCell2D m_LU;        // 左上セルへの参照
        public FluidCell2D m_RU;        // 右上セルへの参照
        public FluidCell2D m_LD;        // 左下セルへの参照
        public FluidCell2D m_RD;        // 右下セルへの参照

        Random m_Rnd = new Random();

        //----------------------------------
        // コンストラクタ
        //----------------------------------
        public FluidCell2D()
        {
            m_Color = new Vector4[2];
            m_Color[0].X = 0.0f;
            m_Color[1].X = 0.0f;

            m_Dencity = new float[2];
            m_Dencity[0] = 0.0f;
            m_Dencity[1] = 0.0f;

        }

        //----------------------------------
        // 位置を指定して初期化
        //----------------------------------
        public void setPos(int x, int y, ref FluidCell2D[][] grid, Vector4 col )
        {
            // 位置を設定
            m_PosX = x;
            m_PosY = y;
            // 隣接セルへの参照を保存
            m_Up        = grid[x][y - 1];
            m_Left      = grid[x - 1][y];
            m_Right     = grid[x + 1][y];
            m_Down      = grid[x][y + 1];
            m_LU = grid[x-1][y - 1];
            m_RU = grid[x+1][y - 1];
            m_LD = grid[x-1][y + 1];
            m_RD = grid[x+1][y + 1];

            // 密度をクリア
            m_Dencity[0] = 0.0f;
            m_Dencity[1] = 0.0f;
        }

        //----------------------------------
        // 内容のクリア
        //----------------------------------
        public void clear( ){
            m_Pressure = 0;
            m_Velocity.X = 0;
            m_Velocity.Y = 0;
        }

        //----------------------------------
        // 圧力を更新
        //----------------------------------
        public void updatePressure()
        {
            // 壁なら
            if (m_bWall)
            {
                // 周りの平均を取り、圧力加算は無視する 
                m_Pressure = (m_Left.m_Pressure + m_Right.m_Pressure + m_Up.m_Pressure + m_Down.m_Pressure) * 0.25f;
                m_PressurePlus = 0;
            }
            else
            {
                // 上下左右の速度差から、圧力を更新
                float px, py;
                px = (m_Left.m_Velocity.X - m_Right.m_Velocity.X);
                py = (m_Up.m_Velocity.Y - m_Down.m_Velocity.Y);
                m_Pressure = (px + py) * 0.99f;
                m_Pressure += m_PressurePlus;
                m_PressurePlus = 0;
            }
        }

        //----------------------------------
        // 速度を更新
        //----------------------------------
        public void updateVelocity(NoiseTexture noiseTex, int noiseOfs)
        {
            // 壁なら
            if (m_bWall)
            {
                // 流れない
                m_Velocity.X = m_Velocity.Y = 0.0f;
            }
            else
            {
                // 上下左右の圧力差から、速度を更新
                m_Velocity.X += (m_Left.m_Pressure - m_Right.m_Pressure) * 0.25f;
                m_Velocity.Y += (m_Up.m_Pressure - m_Down.m_Pressure) * 0.25f;
            }
            
            // カールノイズ適用(速度が小さすぎるときは影響を受けすぎないように調整)
            float vel = 0.00025f + (m_Velocity.X * m_Velocity.X + m_Velocity.Y * m_Velocity.Y);
            if (vel > 0.0012f) vel = 0.0012f;
            int cx = m_PosX + noiseOfs;
            int cy = m_PosY + noiseOfs;
            if (cx >= noiseTex.m_Width) cx -= noiseTex.m_Width;     // リピート処理
            if (cy >= noiseTex.m_Height) cy -= noiseTex.m_Height;   // リピート処理

            // 速度にカールノイズを加算
            m_Velocity += noiseTex.m_VecBuf[cx][cy] * 2.0f * vel;
            m_Velocity *= 0.997f;
        }

        //----------------------------------
        // 粘性項を更新
        //----------------------------------
        public void updateViscosity()
        {
            // 周りの速度との差分で自分の速度を更新
            float mul = 0.1f;
            m_Velocity.X +=
                (
                    (m_Left.m_Velocity.X - m_Velocity.X) +
                    (m_Right.m_Velocity.X - m_Velocity.X) +
                    (m_Up.m_Velocity.X - m_Velocity.X) +
                    (m_Down.m_Velocity.X - m_Velocity.X)
                ) * mul;

            m_Velocity.Y +=
                (
                    (m_Left.m_Velocity.Y - m_Velocity.Y) +
                    (m_Right.m_Velocity.Y - m_Velocity.Y) +
                    (m_Up.m_Velocity.Y - m_Velocity.Y) +
                    (m_Down.m_Velocity.Y - m_Velocity.Y)
                ) * mul;

        }

        //----------------------------------
        // 密度の移流を更新
        //----------------------------------
        public void updateDencity(ref FluidCell2D[][] grid, int gridMax, int src, int dst)
        {
            float timeStep = 10.0f;
            float fx = m_PosX - m_Velocity.X * timeStep;
            float fy = m_PosY - m_Velocity.Y * timeStep;

            int ix = (int)fx;
            int iy = (int)fy;
            float dx = fx - ix;
            float dy = fy - iy;

            float dxi = 1.0f - dx;
            float dyi = 1.0f - dy;
            if (ix < 0) { ix = 0; dx = 0.0f; }
            if (iy < 0) { iy = 0; dy = 0.0f; }
            if (ix >= gridMax - 1) { ix = gridMax - 2; dx = 1.0f; }
            if (iy >= gridMax - 1) { iy = gridMax - 2; dy = 1.0f; }

            m_Dencity[dst] = 
                grid[ix  ][iy  ].m_Dencity[src] * dxi * dyi +
                grid[ix+1][iy  ].m_Dencity[src] * dx  * dyi +
                grid[ix  ][iy+1].m_Dencity[src] * dxi * dy  +
                grid[ix+1][iy+1].m_Dencity[src] * dx  * dy  ;
            
            m_PressurePlus += m_Dencity[dst] * 0.10f;
            m_Dencity[dst] += m_Pressure * 0.10f;
             
            m_Dencity[dst] *= 0.985f;
        }

        //----------------------------------
        // 色の流れを更新
        //----------------------------------
        public float getAddVect(ref FluidCell2D[][] grid, int gridMax, int src, float timeStep)
        {
            float fx = m_PosX - m_Velocity.X * timeStep;
            float fy = m_PosY - m_Velocity.Y * timeStep;

            int ix = (int)fx;
            int iy = (int)fy;
            float dx = fx - ix;
            float dy = fy - iy;
            float dxi = 1.0f - dx;
            float dyi = 1.0f - dy;
            if (ix < 0) ix = 0;
            if (iy < 0) iy = 0;
            if (ix >= gridMax - 1) ix = gridMax - 2;
            if (iy >= gridMax - 1) iy = gridMax - 2;

            float ret = grid[ix][iy].m_Color[src].X * dxi * dyi +
                            grid[ix + 1][iy].m_Color[src].X * dx * dyi +
                            grid[ix][iy + 1].m_Color[src].X * dxi * dy +
                            grid[ix + 1][iy + 1].m_Color[src].X * dx * dy;

            return ret;
        }

        //----------------------------------
        // 色の流れを更新
        //----------------------------------
        public void updateColorFlow(ref FluidCell2D[][] grid, int gridMax, int src, int dst, NoiseTexture noiseTex, int noiseOfs)
        {
            // 色情報をMacCormackスキーム(GPU Gems3 p573)で移流
            float timeStep = 0.75f;
            int bType = 1;
            if (bType == 0)
            {
                m_Color[dst].X = getAddVect(ref grid, gridMax, src, timeStep);
            }
            else{
            float ret0 = getAddVect( ref grid, gridMax, src, timeStep);
            float ret1 = getAddVect( ref grid, gridMax, src, -timeStep);

            m_Color[dst].X = ret0 + 0.5f * (m_Color[src].X - ret1);

            Vector2 tmpVel = m_Velocity;
            {
                // カールノイズ適用
                float vel = (m_Velocity.X * m_Velocity.X + m_Velocity.Y * m_Velocity.Y);
                if (vel > 0.0165f) vel = 0.0165f;
                int cx = m_PosX + noiseOfs;
                int cy = m_PosY + noiseOfs;
                if (cx >= noiseTex.m_Width) cx -= noiseTex.m_Width;
                if (cy >= noiseTex.m_Height) cy -= noiseTex.m_Height;
                tmpVel += noiseTex.m_VecBuf[cx][cy] * 0.855f * vel;
            }

            // 移流元の最大値と最小値を取得
            float fx = m_PosX - tmpVel.X * timeStep;
            float fy = m_PosY - tmpVel.Y * timeStep;

            int ix = (int)fx;
            int iy = (int)fy;
            if (ix < 0) ix = 0;
            if (iy < 0) iy = 0;
            if (ix >= gridMax - 1) ix = gridMax - 2;
            if (iy >= gridMax - 1) iy = gridMax - 2;
            
            FluidCell2D d0  = grid[ix  ][iy  ];
            FluidCell2D d1  = grid[ix  ][iy+1];
            FluidCell2D d2  = grid[ix+1][iy  ];
            FluidCell2D d3  = grid[ix+1][iy+1];

            float minX = d0.m_Color[src].X;
            if (d1.m_Color[src].X < minX) minX = d1.m_Color[src].X;
            if (d2.m_Color[src].X < minX) minX = d2.m_Color[src].X;
            if (d3.m_Color[src].X < minX) minX = d3.m_Color[src].X;
            
            float maxX = m_Up.m_Color[src].X;
            if (d1.m_Color[src].X > maxX) maxX = d1.m_Color[src].X;
            if (d2.m_Color[src].X > maxX) maxX = d2.m_Color[src].X;
            if (d3.m_Color[src].X > maxX) maxX = d3.m_Color[src].X;

            if (m_Color[dst].X < minX) m_Color[dst].X = minX;
            if (m_Color[dst].X > maxX) m_Color[dst].X = maxX;
            }

            // 少しづつ明るさが減衰するように
            m_Color[dst].X *= 0.99650f;
            m_Color[dst].Y = m_Color[dst].X;
            m_Color[dst].Z = m_Color[dst].X;
            m_Color[dst].W = m_Color[dst].X;
            
            // 色の濃いところは密度を上げる
            m_PressurePlus += m_Color[dst].X * 0.025f;
            // 色の濃いところは上昇気流を発生させる
            m_Velocity.Y += m_Color[dst].X * 0.00275f;
        }

        //----------------------------------
        // 描画
        //----------------------------------
        public void draw()
        {
            if ((m_PosX % 3 )== 0 && (m_PosY%3) == 0)
            {
                if (Math.Abs(m_Velocity.X) > 0.001 || Math.Abs(m_Velocity.Y) > 0.001)
                {
                    Vector3 st = new Vector3(m_PosX, m_PosY, 0.0f);
                    Vector3 ed = st + new Vector3(m_Velocity.X, m_Velocity.Y, 0.0f) * 50.0f;
                    Game1.DrawLine.add(st, ed, Color.DarkGray);
                }
            }
        }

    }

    //----------------------------------
    // 2D流体クラス
    //----------------------------------
    class Fluid2D
    {
        public FluidCell2D[][] m_Grid;      // セルの2次元配列
        public int m_GridSize;              // グリッドの幅と高さ
        public Texture2D[] m_Tex2D;         // 表示用2Dテクスチャ
        public int m_Tex2DIdxMax = 5;       // テクスチャ書き込み時にロックで待ちが発生しないように複数バッファを使いまわす
        public int m_Tex2DIdxWrite = 2;     // テクスチャ書き込み番号
        public int m_Tex2DIdxUse = 0;       // 使用中テクスチャ番号

        //----------------------------------
        // コンストラクタ
        //----------------------------------
        public Fluid2D(int gridSize)
        {
            m_GridSize = gridSize;
            m_Grid = new FluidCell2D[m_GridSize][];

            // グリッドを生成
            for( int x=0; x max) x = max;
            if (y > max) y = max;
            m_Grid[x][y].m_Color[0] = color;
            m_Grid[x][y].m_Color[1] = color;
        }

        //----------------------------------
        // 指定位置に力を加える
        //----------------------------------
        public void addPow( Vector2 pos, Vector2 pow, int size ){
            int sx = (int)pos.X;
            int ex = sx + size;
            int sy = (int)pos.Y;
            int ey = sy + size;
            if (sx < 1) sx = 1;
            if (sy < 1) sy = 1;
            int max = m_GridSize - 1;
            if (ex > max) ex = max;
            if (ey > max) ey = max;
            for (int x = sx; x < ex; x++)
            {
                for (int y = sy; y < ey; y++)
                {
                    m_Grid[x][y].m_Velocity += pow;
                }
            }

        }

        //----------------------------------
        // 指定位置の力を即値で設定
        //----------------------------------
        public void setPow( Vector2 pos, Vector2 pow, int size ){
            int sx = (int)pos.X - size/2;
            int ex = sx + size;
            int sy = (int)pos.Y - size/2;
            int ey = sy + size;
            if (sx < 1) sx = 1;
            if (sy < 1) sy = 1;
            int max = m_GridSize - 1;
            if (ex > max) ex = max;
            if (ey > max) ey = max;
            for (int x = sx; x < ex; x++)
            {
                for (int y = sy; y < ey; y++)
                {
                    m_Grid[x][y].m_Velocity = pow;
                }
            }

        }

        //----------------------------------
        // 指定位置を壁に設定
        //----------------------------------
        public void setWall(Vector2 pos, int size, bool bWall)
        {
            int sx = (int)pos.X;
            int ex = sx + size;
            int sy = (int)pos.Y;
            int ey = sy + size;
            if (sx < 1) sx = 1;
            if (sy < 1) sy = 1;
            int max = m_GridSize - size - 1;
            if (ex > max) ex = max;
            if (ey > max) ey = max;
            for (int x = sx; x < ex; x++)
            {
                for (int y = sy; y < ey; y++)
                {
                    m_Grid[x][y].m_bWall = bWall;
                }
            }

        }
        
        //----------------------------------
        // 指定位置にガスを追加
        //----------------------------------
        public void addGus(int bufIdx, Vector2 pos, int size, float add)
        {
            int sx = (int)pos.X;
            int ex = sx + size;
            int sy = (int)pos.Y;
            int ey = sy + size;
            if (sx < 1) sx = 1;
            if (sy < 1) sy = 1;
            int max = m_GridSize - size - 1;
            if (ex > max) ex = max;
            if (ey > max) ey = max;
            for (int x = sx; x < ex; x++)
            {
                for (int y = sy; y < ey; y++)
                {
                    m_Grid[x][y].m_Color[bufIdx].X += add;
                    m_Grid[x][y].m_Color[bufIdx].Y += add;
                    m_Grid[x][y].m_Color[bufIdx].Z += add;
                    m_Grid[x][y].m_Color[bufIdx].W += add;
                }
            }

        }

        //----------------------------------
        // 表示用の2Dテクスチャを生成
        //----------------------------------
        public void CreateTex2d(int bufIdx)
        {
            Color[] ary = new Color[m_GridSize * m_GridSize];
            for (int x = 0; x < m_GridSize; x++)
            {
                for (int y = 0; y < m_GridSize; y++)
                {
                    int px = x + 1;
                    int py = y + 1;
                    if (px >= m_GridSize) px = m_GridSize-1;
                    if (py >= m_GridSize) py = m_GridSize-1;
                    int my = m_GridSize-1 - y;
                    float c = m_Grid[x][my].m_Color[bufIdx].X;
                    ary[x + y * m_GridSize] = new Color(c*1.5f, c*0.65f, c*0.35f, c);
                }
            }
            
            m_Tex2DIdxWrite++;
            m_Tex2DIdxUse++;
            if( m_Tex2DIdxWrite >= m_Tex2DIdxMax ) m_Tex2DIdxWrite = 0;
            if( m_Tex2DIdxUse >= m_Tex2DIdxMax ) m_Tex2DIdxUse = 0;
            m_Tex2D[m_Tex2DIdxWrite].SetData(ary);
        }
        
    }
}