物理演算、その1

■とにかく、物理演算やってみましょう
ソース説明は次回からで、動かしてみましょう

Game1.cs :メインの部分
BoxObject.cs :物理演算する箱のクラス
Vec2.cs :2Dベクトルクラス(前回作ったクラス)

■ソース
Game1.cs

namespace WindowsGame
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        ContentManager content;
        private const int VERTICES_SIZE = 2;
        VertexPositionColor[] vertices = new VertexPositionColor[VERTICES_SIZE];
        VertexDeclaration vdecl;
        BasicEffect basicEffect;

        BoxObject[] obj = new BoxObject[]{
            new BoxObject(100.0f, 100.0f),
            new BoxObject(140.0f, 160.0f),
            new BoxObject(130.0f,   0.0f),
            new BoxObject(120.0f,  50.0f),
            new BoxObject(  0.0f,  40.0f),
            new BoxObject(200.0f,  40.0f),
            new BoxObject(400.0f, 140.0f),
            new BoxObject(300.0f, 140.0f),
            new BoxObject(440.0f,  50.0f),
            new BoxObject(500.0f,  60.0f),
            new BoxObject(430.0f, 130.0f),
            new BoxObject(510.0f, 160.0f),
            new BoxObject(200.0f,   0.0f),
            new BoxObject(210.0f, 300.0f)};

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            content = new ContentManager(Services);
        }

        protected override void Initialize()
        {
            vdecl = new VertexDeclaration(graphics.GraphicsDevice, VertexPositionColor.VertexElements);
            basicEffect = new BasicEffect(graphics.GraphicsDevice, null);
            basicEffect.VertexColorEnabled = true;

            basicEffect.World = Matrix.Identity;
            basicEffect.View = Matrix.Identity;
            //原点( 0, 0 )を画面右上に設定
            basicEffect.Projection = Matrix.CreateOrthographicOffCenter(
                0.0f,                            // 左座標
                this.Window.ClientBounds.Width,  // 右座標 Window幅
                this.Window.ClientBounds.Height, // 下座標 Window高さ
                0.0f,              // 上座標
                0.0f,                            // ニアクリップの距離
                1.0f);                           // ファークリップの距離
            base.Initialize();
        }

        protected override void Update(GameTime gameTime)
        {
            foreach (BoxObject bobjI in obj)
            {
                bobjI.Exec();
                foreach (BoxObject bobjJ in obj)
                    if (bobjI != bobjJ)
                        bobjI.CheckObj(bobjJ);
            }
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            Color lineColor = new Color(0, 0, 0);
            foreach (BoxObject bobjI in obj)
            {
                Line(bobjI.pos[0].X, bobjI.pos[0].Y,
                     bobjI.pos[1].X, bobjI.pos[1].Y, lineColor);
                Line(bobjI.pos[1].X, bobjI.pos[1].Y,
                     bobjI.pos[2].X, bobjI.pos[2].Y, lineColor);
                Line(bobjI.pos[2].X, bobjI.pos[2].Y,
                     bobjI.pos[3].X, bobjI.pos[3].Y, lineColor);
                Line(bobjI.pos[3].X, bobjI.pos[3].Y,
                     bobjI.pos[0].X, bobjI.pos[0].Y, lineColor);
            }
            
            base.Draw(gameTime);
        }

        public void Line(float linePosX1, float linePosY1, float linePosX2, float linePosY2, Color lineColor)
        {
            vertices[0].Position.X = linePosX1;
            vertices[0].Position.Y = linePosY1;
            vertices[0].Position.Z = 0;
            vertices[0].Color = lineColor;

            vertices[1].Position.X = linePosX2;
            vertices[1].Position.Y = linePosY2;
            vertices[1].Position.Z = 0;
            vertices[1].Color = lineColor;

            basicEffect.Begin();
            graphics.GraphicsDevice.VertexDeclaration = vdecl;
            basicEffect.CurrentTechnique.Passes[0].Begin();
            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.LineList, vertices, 0, 1);
            basicEffect.CurrentTechnique.Passes[0].End();
            basicEffect.End();
        }
    }
}

BoxObject.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace WindowsGame
{
    class BoxObject
    {
        const int dispWidth = 800;
        const int dispHeight = 600;
        const int width  = 75;          // BOXの幅
        const int height = 75;          // BOXの高さ
        const int boxPotinMax = 4;      // 頂点数
        const int boxLineMax = 6;       // ライン数
        private Vec2 nx = new Vec2(1, 0);
        private Vec2 ny = new Vec2(0, 1 );
        private Vec2 cen;            //    重心
        public  Vec2[] pos = new Vec2[boxPotinMax];         // 各頂点の座標
        private Vec2[] posOld = new Vec2[boxPotinMax];
        private Vec2[] move = new Vec2[boxPotinMax];
        private float[] distance = new float[boxLineMax];    // 辺の長さ
        private bool[] control = new bool[boxPotinMax];     // 制御
        int[][] boxIndex = new int[][] { new int[] { 0, 1 }, 
                                         new int[] { 1, 2 },
                                         new int[] { 2, 3 },
                                         new int[] { 3, 0 },
                                         new int[] { 0, 2 },
                                         new int[] { 1, 3 } };
        public BoxObject( float x, float y )
        {
            Vec2    cc  = new Vec2( x, y );

            pos[ 0 ]    = cc;
            pos[ 1 ]    = cc + nx * width;
            pos[ 2 ]    = cc + nx * width + ny * height;
            pos[ 3 ]    = cc +              ny * height;
            cen = (pos[0] + pos[1] + pos[2] + pos[3]) / boxPotinMax;
            for (int i = 0; i < boxPotinMax; i++)
                move[i] = new Vec2(0, 0);
            for (int i = 0; i < boxLineMax; i++)
                distance[i] = (pos[boxIndex[i][0]] - pos[boxIndex[i][1]]).Abs();
            System.Array.Copy(pos, 0, posOld, 0, boxPotinMax);
        }

        public bool Exec()
        {
            for (int i = 0; i < boxPotinMax; i++)
            {
                pos[i] += move[i];
                pos[i].Y += 0.10f;
                control[i] = false;
            }
            for (int i = 0; i < boxPotinMax; i++)
            {
                if( pos[ i ].Y < 0 ){
                    move[i].X = 0.0f;
                    move[i].Y *= -0.01f;
                    control[i] = true;
                    pos[i].Y = 0;
                }
                if( pos[ i ].Y > dispHeight ){
                    move[i].X = 0.0f;
                    move[i].Y *= -0.01f;
                    control[i] = true;
                    pos[i].Y = dispHeight;
                }
                if( pos[ i ].X < 0 ){
                    move[i].X *= -0.01f;
                    move[i].Y = 0.0f;
                    control[i] = true;
                    pos[i].X = 0;
                }
                if( pos[ i ].X > dispWidth ){
                    move[i].X *= -0.01f;
                    move[i].Y = 0.0f;
                    control[i] = true;
                    pos[i].X = dispWidth;
                }
            }
            //-----------------------------------------
            //    点間の距離を補正する
            for (int i = 0; i < boxLineMax; i++)
            {
                Vec2 d = pos[boxIndex[i][0]] - pos[boxIndex[i][1]];
                float dis = d.Abs();
                d        /= dis;
                d        *=( distance[ i ] - dis ) * 0.5f;
                pos[boxIndex[i][0]] += d;
                pos[boxIndex[i][1]] -= d;
            }
            //-----------------------------------------
            //    重心
            cen = (pos[0] + pos[1] + pos[2] + pos[3]) / boxPotinMax;
            //-----------------------------------------
            //    基準座標系を求める
            nx = ((pos[1] - pos[0]) + (pos[2] - pos[3])).Norm();
            ny = ((pos[3] - pos[0]) + (pos[2] - pos[1])).Norm();
            //-----------------------------------------
            //    各点の再計算
            Vec2 ux = nx * (width * 0.5f);
            Vec2 uy = ny * (height * 0.5f);
            pos[0] = cen - ux - uy;
            pos[1] = cen + ux - uy;
            pos[2] = cen + ux + uy;
            pos[3] = cen - ux + uy;
            //-----------------------------------------
            for (int i = 0; i < boxPotinMax; i++)
            {
                if (!control[i])
                    move[i] = (pos[i] - posOld[i]);
            }
            System.Array.Copy(pos, 0, posOld, 0, boxPotinMax);
            return false;
        }

        public void CheckObj(BoxObject obj)
        {
            Vec2 vel = new Vec2(0, 0);
            Vec2 lpos;
            int    f;
            float[] dis = new float[boxPotinMax];

            for (int i = 0; i < boxPotinMax; i++)
            {
                lpos    = pos[ i ] - obj.pos[ 0 ];
                vel.Set((float)(lpos % obj.nx), (float)(lpos % obj.ny));

                if( vel.X < 0 )
                    continue;
                if( vel.X > width )
                    continue;
                if( vel.Y < 0 )
                    continue;
                if( vel.Y > height )
                    continue;
                dis[0] = vel.X;
                dis[1] = vel.Y;
                dis[2] = width  - vel.X;
                dis[3] = height - vel.Y;
                f    = 0;
                for (int j = 1; j < boxPotinMax; j++)
                {
                    if( dis[j] < dis[f] )
                        f    = j;
                }
                switch( f ){
                case    0:
                    pos[i]    -= obj.nx * vel.X;
                    break;
                case    1:
                    pos[i]    -= obj.ny * vel.Y;
                    break;
                case    2:
                    pos[i]    += obj.nx * ( width  - vel.X );
                    break;
                case    3:
                    pos[i]    += obj.ny * ( height - vel.Y );
                    break;
                }
            }

            for (int i = 0; i < boxPotinMax; i++)
            {
                for (int j = 1; j < boxPotinMax; j++)
                {
                    lpos = pos[boxIndex[i][0]] + (pos[boxIndex[i][1]] - pos[boxIndex[i][0]]) * j / boxPotinMax - obj.pos[0];
                    vel.Set(lpos % obj.nx, lpos % obj.ny);

                    if( vel.X < 0 )
                        continue;
                    if( vel.X > width )
                        continue;
                    if( vel.Y < 0 )
                        continue;
                    if( vel.Y > height )
                        continue;
                    dis[0] = vel.X;
                    dis[1] = vel.Y;
                    dis[2] = width  - vel.X;
                    dis[3] = height - vel.Y;
                    f    = 0;
                    for (int k = 1; k < boxPotinMax; k++)
                    {
                        if (dis[k] < dis[f])
                            f = k;
                    }

                    switch (f)
                    {
                    case    0:
                        pos[boxIndex[i][0]] -= obj.nx * vel.X;
                        pos[boxIndex[i][1]] -= obj.nx * vel.X;
                        break;
                    case    1:
                        pos[boxIndex[i][0]] -= obj.ny * vel.Y;
                        pos[boxIndex[i][1]] -= obj.ny * vel.Y;
                        break;
                    case    2:
                        pos[boxIndex[i][0]] += obj.nx * (width  - vel.X);
                        pos[boxIndex[i][1]] += obj.nx * (width  - vel.X);
                        break;
                    case    3:
                        pos[boxIndex[i][0]] += obj.ny * (height - vel.Y);
                        pos[boxIndex[i][1]] += obj.ny * (height - vel.Y);
                        break;
                    }
                }
            }
        }
    }
}

■結果画面
四角い箱が物理演算で転がりまわります。