ゲームエフェクトデザイナーのブログ | A Real-Time VFX Artist's Blog

About Making Materials on UE, Making Tools with C#, etc

C# 穴の空いた多角形の3Dモデルの描画

前回の多角形ポリゴンに引き続いて、MonoGame(XNA)を使ってC#で穴の空いた多角形ポリゴンを描画してみました。
f:id:moko_03_25:20190114151255g:plain

こういうパイナップル型のモデルを何と呼べば良いんでしょう。リング?
 
「多角形の分割数」を変えれば、四角形>五角形>六角形‥と増やしていけます。
頂点インデックスは法則を見つけて落とし込む感じですね。。

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace MonoGame3DTest2
{
    public class MyGame : Game
    {
        ////////////////////////////////////////
        //                                   //
        // 穴の空いた円ポリゴンを描くサンプル //
        //                                   //
        ////////////////////////////////////////

        GraphicsDeviceManager graphics;
        
        // データがどのように描画されるかを制御するクラス
        BasicEffect effect;

        // 多角形の分割数
        static int polyCircleCount = 12;
        // 中心の穴の半径
        float r = 0.3f;

        // 座標と色の2つのデータを持った頂点情報の構造体
        VertexPositionColor[] vertices = new VertexPositionColor[polyCircleCount * 2];

        // 頂点インデックス指定用
        int[] indices  = new int[polyCircleCount * 2 * 3];
        
        // カメラの制御用
        float angle = 0;
        float height = 0;

        // コンストラクタ
        public MyGame()
        {
            graphics = new GraphicsDeviceManager(this);
        }

        // 表示物の準備
        protected override void LoadContent()
        {
            // 円の頂点情報
            for (int i = 0; i < polyCircleCount; i++)
            {
                // 円を分割した1つ分の角度
                double cut = (Math.PI * 2) / polyCircleCount;
                // 角度
                double theta = cut * i;

                // X, Y の算出
                float x2 = (float)(Math.Cos(theta));
                float y2 = (float)(Math.Sin(theta));
                float x1 = r * x2;
                float y1 = r * y2;

                // 円の内側の頂点
                vertices[i] = new VertexPositionColor
                    (
                        new Vector3(x1, y1, 0),
                        Color.White
                    );

                // 円の外側の頂点
                vertices[i + polyCircleCount] = new VertexPositionColor
                    (
                        new Vector3(x2, y2, 0),
                        Color.White
                    );

                // デバッグ用
                //theta = (theta / Math.PI) *180;
                //float dX1 = (float)(((double)x1 / Math.PI) * 180);
                //float dY1 = (float)(((double)y1 / Math.PI) * 180);
                //float dX2 = (float)(((double)x2 / Math.PI) * 180);
                //float dY2 = (float)(((double)y2 / Math.PI) * 180);
                //Console.WriteLine("i: " + i + " theta: " + theta +
                //" x1: " + dX1 + " y1: " + dY1 +
                //" x2: " + dX2 + " y2: " + dY2);
            }

            // 穴の空いた円ポリゴンのインデックス生成
            int vertexId = 0;
            for (int i = 0; i < polyCircleCount; i++)
            {
                // 一度に三角形2つ分のIndexを指定するので6ずつ増やす
                int i2 = i * 6;

                // 最後のポリゴン生成かどうか判定
                if (vertexId != polyCircleCount - 1)
                {
                    // 三角形1つ目
                    indices[i2]     = vertexId;
                    indices[i2 + 1]    = vertexId + polyCircleCount;
                    indices[i2 + 2]    = vertexId + polyCircleCount + 1;
                    // 三角形2つ目
                    indices[i2 + 3]    = vertexId;
                    indices[i2 + 4]    = vertexId + polyCircleCount + 1;
                    indices[i2 + 5]    = vertexId + 1;
                }
                else
                {
                    // 三角形1つ目
                    indices[i2]     = vertexId;
                    indices[i2 + 1]    = vertexId + polyCircleCount;
                    indices[i2 + 2]    = polyCircleCount;
                    // 三角形2つ目
                    indices[i2 + 3]    = vertexId;
                    indices[i2 + 4]    = polyCircleCount;
                    indices[i2 + 5]    = 0;
                }

                // デバッグ用
                //Console.WriteLine("i: " + i + " vertexId: " + vertexId);

                vertexId++;
            }

            effect = new BasicEffect(GraphicsDevice)
            {
                Projection = Matrix.CreatePerspectiveFieldOfView
                (
                    MathHelper.ToRadians(45),              // 視野角
                    GraphicsDevice.Viewport.AspectRatio,    // アスペクト比(横/縦)
                    1,     // ニアクリップ
                    100        // ファークリップ
                )
            };
        }

        // メモリからの解放
        protected override void UnloadContent()
        {
            effect.Dispose();
        }

        // 計算の更新
        protected override void Update(GameTime gameTime)
        {
            // キーボード制御
            KeyboardState keyboardState = Keyboard.GetState();

            if (keyboardState.IsKeyDown(Keys.Left))
            {
                angle -= MathHelper.ToRadians(2.0f); // 1/60秒に0.5°回転
            }
            if (keyboardState.IsKeyDown(Keys.Right))
            {
                angle += MathHelper.ToRadians(2.0f); // 1/60秒に0.5°回転
            }
            if (keyboardState.IsKeyDown(Keys.Up))
            {
                height += 0.1f; // 1/60秒に0.1移動
            }
            if (keyboardState.IsKeyDown(Keys.Down))
            {
                height -= 0.1f; // 1/60秒に0.1移動
            }
        }

        // 描画
        protected override void Draw(GameTime gameTime)
        {
            // 背景のクリア
            Color c = new Color(100, 110, 60);
            GraphicsDevice.Clear(c);

            // 両面描画
            GraphicsDevice.RasterizerState = RasterizerState.CullNone;

            // カメラの位置
            effect.View = Matrix.CreateLookAt
            (
                new Vector3
                (
                    3 * (float)Math.Sin(angle),
                    height,
                    3 * (float)Math.Cos(angle)
                ),
                new Vector3(0, 0, 0),   //カメラの注視点
                new Vector3(0, 1, 0)    //カメラのUPベクトル
            );

            // 三角形の描画
            foreach (var pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();

                // 3角形を実際に描画する
                GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
                    PrimitiveType.TriangleList,
                    vertices,
                    0,
                    vertices.Length,
                    indices ,
                    0,
                    indices .Length / 3
                    );
            }
        }
    }
}

 
内側と外側で円状に配置する頂点は 半径Cos(角度) と 半径Sin(角度) でXとY座標を出しています。
(外側は 半径=1 にしているため r* は省略しています)
 
例によってこの記述ではポリゴンの面が裏を向いて生成されてしまっていると思います。
(両面描画にしているので表示されていますが)