ボックスとボックスの衝突判定

Page updated :

The page you are currently viewing does not support the selected display language.

衝突判定の中では比較的高速な判定が出来る方法です。回転しない箱型物体、例えばキャラクター以外の家とか置物とかにとても有効です。

操作はティーポットを「←」「→」「↑」「↓」キーで「X」「Z」方向に移動できます。二つのボックスが衝突すると「当たっています!」の文字が表示されます

ボックスとボックスの衝突判定

今回のメインコードファイルを載せます。

MainSample.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace MDXSample
{
    /// <summary>
    /// メインサンプルクラス
    /// </summary>
    public partial class MainSample : IDisposable
    {
        /// <summary>
        /// ティーポット
        /// </summary>
        private Mesh _teapot = null;

        /// <summary>
        /// ティーポット当たり判定ボックスの最小値
        /// </summary>
        private Vector3 _teapotBoxMin = Vector3.Empty;

        /// <summary>
        /// ティーポット当たり判定ボックスの最大値
        /// </summary>
        private Vector3 _teapotBoxMax = Vector3.Empty;

        /// <summary>
        /// ティーポットの当たり判定球実体化用
        /// </summary>
        private Box _teapotBox = null;

        /// <summary>
        /// ティーポットの位置
        /// </summary>
        private Vector3 _teapotPosition = Vector3.Empty;


        /// <summary>
        ///
        /// </summary>
        private Mesh _sphere = null;

        /// <summary>
        /// 球当たり判定ボックスの最小値
        /// </summary>
        private Vector3 _sphereBoxMin = Vector3.Empty;

        /// <summary>
        /// 球当たり判定ボックスの最大値
        /// </summary>
        private Vector3 _sphereBoxMax = Vector3.Empty;

        /// <summary>
        /// 球の当たり判定球実体化用
        /// </summary>
        private Box _sphereBox = null;

        /// <summary>
        /// 球の位置
        /// </summary>
        private Vector3 _spherePosition = new Vector3(-2.0f, 0.0f, -3.0f);


        /// <summary>
        /// 当たり判定フラグ
        /// </summary>
        private bool _hitFlag = false;


        /// <summary>
        /// アプリケーションの初期化
        /// </summary>
        /// <param name="topLevelForm">トップレベルウインドウ</param>
        /// <returns>全ての初期化がOKなら true, ひとつでも失敗したら false を返すようにする</returns>
        /// <remarks>
        /// false を返した場合は、自動的にアプリケーションが終了するようになっている
        /// </remarks>
        public bool InitializeApplication(MainForm topLevelForm)
        {
            // フォームの参照を保持
            this._form = topLevelForm;

            // 入力イベント作成
            this.CreateInputEvent(topLevelForm);

            try
            {
                // Direct3D デバイス作成
                this.CreateDevice(topLevelForm);

                // フォントの作成
                this.CreateFont();

                // XYZライン作成
                this.CreateXYZLine();
            }
            catch (DirectXException ex)
            {
                // 例外発生
                MessageBox.Show(ex.ToString(), "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }


            // カメラの位置セット
            this.SetCameraPosition(15.0f, 270.0f, 80.0f);


            // ティーポット作成
            this._teapot = Mesh.Teapot(this._device);

            // ティーポットの包括ボックスを計算し、大きさ取得
            using (VertexBuffer vb = this._teapot.VertexBuffer)
            {
                // 頂点バッファをロックしてストリーム取得
                using (GraphicsStream vertexData = vb.Lock(0, 0, LockFlags.None))
                {
                    // ティーポットの包括ボックスを計算し、大きさ取得
                    Geometry.ComputeBoundingBox(vertexData,
                        this._teapot.NumberVertices, this._teapot.VertexFormat,
                        out this._teapotBoxMin, out this._teapotBoxMax);

                    vertexData.Close();
                }

                // 頂点ロック解除
                vb.Unlock();
            }

            // 見やすいように当たり判定ボックスを作成
            this._teapotBox = Box.Create(this._device, this._teapotBoxMin, this._teapotBoxMax);


            // 球作成
            this._sphere = Mesh.Sphere(this._device, 1.5f, 16, 16);

            // 球の包括ボックスを計算し、大きさ取得
            using (VertexBuffer vb = this._sphere.VertexBuffer)
            {
                // 頂点バッファをロックしてストリーム取得
                using (GraphicsStream vertexData = vb.Lock(0, 0, LockFlags.None))
                {
                    // 球の包括ボックスを計算し、大きさ取得
                    Geometry.ComputeBoundingBox(vertexData,
                        this._sphere.NumberVertices, this._sphere.VertexFormat,
                        out this._sphereBoxMin, out this._sphereBoxMax);

                    vertexData.Close();
                }

                // 頂点ロック解除
                vb.Unlock();
            }

            // 見やすいように当たり判定ボックスを作成
            this._sphereBox = Box.Create(this._device, this._sphereBoxMin, this._sphereBoxMax);


            // アルファブレンディング方法を設定
            this._device.RenderState.SourceBlend = Blend.SourceAlpha;
            this._device.RenderState.DestinationBlend = Blend.InvSourceAlpha;

            // ライトの設定
            this.SettingLight();

            return true;
        }

        /// <summary>
        /// デバイスがロストしたとき
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void device_DeviceLost(object sender, EventArgs e)
        {
        }
        /// <summary>
        /// デバイスがリセットしたとき
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void device_DeviceReset(object sender, EventArgs e)
        {
        }

        /// <summary>
        /// 更新処理
        /// </summary>
        public void Update()
        {
            // アプリケーションの終了操作
            if (this._keys[(int)Keys.Escape])
            {
                this._form.Close();
                return;
            }

            // カメラの設定
            this.SettingCamera();


            // ティーポットの移動
            if (this._keys[(int)Keys.Left])
            {
                this._teapotPosition.X -= 0.1f;
            }
            if (this._keys[(int)Keys.Right])
            {
                this._teapotPosition.X += 0.1f;
            }
            if (this._keys[(int)Keys.Down])
            {
                this._teapotPosition.Z -= 0.1f;
            }
            if (this._keys[(int)Keys.Up])
            {
                this._teapotPosition.Z += 0.1f;
            }


            // ワールド座標でのボックスの面の位置を計算しておく
            Vector3 worldTeapotMin = this._teapotPosition + this._teapotBoxMin;
            Vector3 worldTeapotMax = this._teapotPosition + this._teapotBoxMax;
            Vector3 worldSphereMin = this._spherePosition + this._sphereBoxMin;
            Vector3 worldSphereMax = this._spherePosition + this._sphereBoxMax;

            // 各6つの面の位置関係を調べる
            if (worldTeapotMin.X < worldSphereMax.X &&
                worldTeapotMin.Y < worldSphereMax.Y &&
                worldTeapotMin.Z < worldSphereMax.Z &&
                worldTeapotMax.X > worldSphereMin.X &&
                worldTeapotMax.Y > worldSphereMin.Y &&
                worldTeapotMax.Z > worldSphereMin.Z)
            {
                this._hitFlag = true;
            }
            else
            {
                this._hitFlag = false;
            }
        }

        /// <summary>
        /// 描画処理
        /// </summary>
        public void Draw()
        {
            // デバイスが使える状態か確認する
            if (!this.EnsureDevice())
            {
                return;
            }

            // 描画内容を単色でクリアし、Zバッファもクリア
            this._device.Clear(ClearFlags.ZBuffer | ClearFlags.Target, Color.DarkBlue, 1.0f, 0);

            // 「BeginScene」と「EndScene」の間に描画内容を記述する
            this._device.BeginScene();


            // XYZライン描画
            this.RenderXYZLine();


            Material material;


            // 不透明マテリアル使用
            material = new Material();
            material.Diffuse = Color.White;
            material.Ambient = Color.FromArgb(255, 128, 128, 128);
            this._device.Material = material;

            // アルファブレンディングを無効にする
            this._device.SetRenderState(RenderStates.AlphaBlendEnable, false);

            // ティーポット描画
            this._device.SetTransform(TransformType.World, Matrix.Translation(this._teapotPosition));
            this._teapot.DrawSubset(0);

            // 球描画
            this._device.SetTransform(TransformType.World, Matrix.Translation(this._spherePosition));
            this._sphere.DrawSubset(0);


            // 半透明マテリアル使用
            material = new Material();
            material.Diffuse = Color.FromArgb(128, 192, 192, 192);
            material.Ambient = Color.FromArgb(128, 0, 0, 128);
            this._device.Material = material;

            // アルファブレンディングを有効にする
            this._device.SetRenderState(RenderStates.AlphaBlendEnable, true);

            // ティーポット包括ボックス描画
            this._device.SetTransform(TransformType.World, Matrix.Translation(this._teapotPosition));
            this._teapotBox.Draw();

            // 球包括ボックス描画
            this._device.SetTransform(TransformType.World, Matrix.Translation(this._spherePosition));
            this._sphereBox.Draw();



            // 文字列の描画
            this.RenderText();

            // 描画はここまで
            this._device.EndScene();

            // 実際のディスプレイに描画
            this._device.Present();
        }

        /// <summary>
        /// テキストの描画
        /// </summary>
        private void RenderText()
        {
            this.DrawText("[Escape]終了", 0, 0, Color.White);
            this.DrawText("θ:" + this._lensPosTheta, 0, 12, Color.White);
            this.DrawText("φ:" + this._lensPosPhi, 0, 24, Color.White);
            this.DrawText("Teapotサイズ:" +
                " X=" + (this._teapotBoxMax.X - this._teapotBoxMin.X).ToString() +
                " Y=" + (this._teapotBoxMax.Y - this._teapotBoxMin.Y).ToString() +
                " Z=" + (this._teapotBoxMax.Z - this._teapotBoxMin.Z).ToString(),
                0, 36, Color.White);
            this.DrawText("Box   サイズ:" +
                " X=" + (this._sphereBoxMax.X - this._sphereBoxMin.X).ToString() +
                " Y=" + (this._sphereBoxMax.Y - this._sphereBoxMin.Y).ToString() +
                " Z=" + (this._sphereBoxMax.Z - this._sphereBoxMin.Z).ToString(),
                0, 48, Color.White);
            if (this._hitFlag)
            {
                this.DrawText("当たっています!", 0, 60, Color.Yellow);
            }
            else
            {
                this.DrawText("当たっていません。", 0, 60, Color.LightBlue);
            }
        }

        /// <summary>
        /// リソースの破棄をするために呼ばれる
        /// </summary>
        public void Dispose()
        {
            // 作成したメッシュの破棄
            this.SafeDispose(this._teapot);
            this.SafeDispose(this._teapotBox);
            this.SafeDispose(this._sphere);
            this.SafeDispose(this._sphereBox);
            
            // リソースの破棄
            this.DisposeResource();
        }
    }
}
MainSamplePartial.cs ファイルのコードはこちらです。

MainSamplePartial.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace MDXSample
{
    public partial class MainSample
    {
        /// <summary>
        /// メインフォーム
        /// </summary>
        private MainForm _form = null;

        /// <summary>
        /// Direct3D デバイス
        /// </summary>
        private Device _device = null;

        /// <summary>
        /// Direct3D 用フォント
        /// </summary>
        private Microsoft.DirectX.Direct3D.Font _font = null;

        /// <summary>
        /// キーのプレス判定
        /// </summary>
        private bool[] _keys = new bool[256];

        /// <summary>
        /// 1つ前のマウスの位置
        /// </summary>
        private Point _oldMousePoint = Point.Empty;

        /// <summary>
        /// カメラレンズの位置(R)
        /// </summary>
        private float _lensPosRadius = 5.0f;

        /// <summary>
        /// カメラレンズの位置(θ)
        /// </summary>
        private float _lensPosTheta = 300.0f;

        /// <summary>
        /// カメラレンズの位置(φ)
        /// </summary>
        private float _lensPosPhi = 30.0f;

        /// <summary>
        /// XYZライン用頂点バッファ
        /// </summary>
        private VertexBuffer _xyzLineVertexBuffer = null;


        /// <summary>
        /// 入力イベント作成
        /// </summary>
        /// <param name="topLevelForm">トップレベルウインドウ</param>
        private void CreateInputEvent(MainForm topLevelForm)
        {
            // キーイベント作成
            topLevelForm.KeyDown += new KeyEventHandler(this.form_KeyDown);
            topLevelForm.KeyUp += new KeyEventHandler(this.form_KeyUp);

            // マウス移動イベント
            topLevelForm.MouseMove += new MouseEventHandler(this.form_MouseMove);
        }

        /// <summary>
        /// キーボードのキーを押した瞬間
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void form_KeyDown(object sender, KeyEventArgs e)
        {
            // 押されたキーコードのフラグを立てる
            if ((int)e.KeyCode < this._keys.Length)
            {
                this._keys[(int)e.KeyCode] = true;
            }
        }
        /// <summary>
        /// キーボードのキーを放した瞬間
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void form_KeyUp(object sender, KeyEventArgs e)
        {
            // 放したキーコードのフラグを下ろす
            if ((int)e.KeyCode < this._keys.Length)
            {
                this._keys[(int)e.KeyCode] = false;
            }
        }

        /// <summary>
        /// マウス移動イベント
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void form_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                // 回転
                this._lensPosTheta -= e.Location.X - this._oldMousePoint.X;
                this._lensPosPhi += e.Location.Y - this._oldMousePoint.Y;

                // φに関しては制限をつける
                if (this._lensPosPhi >= 90.0f)
                {
                    this._lensPosPhi = 89.9999f;
                }
                else if (this._lensPosPhi <= -90.0f)
                {
                    this._lensPosPhi = -89.9999f;
                }
            }
            // マウスの位置を記憶
            this._oldMousePoint = e.Location;
        }

        /// <summary>
        /// Direct3D デバイスの作成
        /// </summary>
        /// <param name="topLevelForm">トップレベルウインドウ</param>
        private void CreateDevice(MainForm topLevelForm)
        {
            // PresentParameters。デバイスを作成する際に必須
            // どのような環境でデバイスを使用するかを設定する
            PresentParameters pp = new PresentParameters();

            // ウインドウモードなら true、フルスクリーンモードなら false を指定
            pp.Windowed = true;

            // スワップ効果。とりあえず「Discard」を指定。
            pp.SwapEffect = SwapEffect.Discard;

            // 深度ステンシルバッファ。3Dでは前後関係があるので通常 true
            pp.EnableAutoDepthStencil = true;

            // 自動深度ステンシル サーフェイスのフォーマット。
            // 「D16」に対応しているビデオカードは多いが、前後関係の精度があまりよくない。
            // できれば「D24S8」を指定したいところ。
            pp.AutoDepthStencilFormat = DepthFormat.D16;

            try
            {
                // デバイスの作成
                this.CreateDevice(topLevelForm, pp);
            }
            catch (DirectXException ex)
            {
                // 例外発生
                throw ex;
            }
        }
        /// <summary>
        /// Direct3D デバイスの作成
        /// </summary>
        /// <param name="topLevelForm">トップレベルウインドウ</param>
        /// <param name="presentationParameters">PresentParameters 構造体</param>
        private void CreateDevice(MainForm topLevelForm, PresentParameters presentationParameters)
        {
            // 実際にデバイスを作成します。
            // 常に最高のパフォーマンスで作成を試み、
            // 失敗したら下位パフォーマンスで作成するようにしている。
            try
            {
                // ハードウェアによる頂点処理、ラスタライズを行う
                // 最高のパフォーマンスで処理を行えます。
                // ビデオカードによっては実装できない処理が存在します。
                this._device = new Device(0, DeviceType.Hardware, topLevelForm.Handle,
                    CreateFlags.HardwareVertexProcessing, presentationParameters);
            }
            catch (DirectXException ex1)
            {
                // 作成に失敗
                Debug.WriteLine(ex1.ToString());
                try
                {
                    // ソフトウェアによる頂点処理、ハードウェアによるラスタライズを行う
                    this._device = new Device(0, DeviceType.Hardware, topLevelForm.Handle,
                        CreateFlags.SoftwareVertexProcessing, presentationParameters);
                }
                catch (DirectXException ex2)
                {
                    // 作成に失敗
                    Debug.WriteLine(ex2.ToString());
                    try
                    {
                        // ソフトウェアによる頂点処理、ラスタライズを行う
                        // パフォーマンスはとても低いです。
                        // その代わり、ほとんどの処理を制限なく行えます。
                        this._device = new Device(0, DeviceType.Reference, topLevelForm.Handle,
                            CreateFlags.SoftwareVertexProcessing, presentationParameters);
                    }
                    catch (DirectXException ex3)
                    {
                        // 作成に失敗
                        // 事実上デバイスは作成できません。
                        throw ex3;
                    }
                }
            }

            // デバイスの状態変更イベントを作成
            this._device.DeviceLost += new EventHandler(this.device_DeviceLost);
            this._device.DeviceReset += new EventHandler(this.device_DeviceReset);
        }

        /// <summary>
        /// フォントの作成
        /// </summary>
        private void CreateFont()
        {
            try
            {
                // フォントデータの構造体を作成
                FontDescription fd = new FontDescription();

                // 構造体に必要なデータをセット
                fd.Height = 12;
                fd.FaceName = "MS ゴシック";

                // フォントを作成
                this._font = new Microsoft.DirectX.Direct3D.Font(this._device, fd);
            }
            catch (DirectXException ex)
            {
                // 例外発生
                throw ex;
            }
        }

        /// <summary>
        /// XYZライン作成
        /// </summary>
        private void CreateXYZLine()
        {
            // 6つ分の頂点を作成
            this._xyzLineVertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionColored),
                6, this._device, Usage.None, CustomVertex.PositionColored.Format, Pool.Managed);

            // 頂点バッファをロックして、位置、色情報を書き込む
            using (GraphicsStream data = this._xyzLineVertexBuffer.Lock(0, 0, LockFlags.None))
            {
                // 今回は各XYZのラインを原点(0.0f, 0.0f, 0.0f)からプラス方向に 10.0f 伸びた線を作成
                data.Write(new CustomVertex.PositionColored(0.0f, 0.0f, 0.0f, Color.Red.ToArgb()));
                data.Write(new CustomVertex.PositionColored(10.0f, 0.0f, 0.0f, Color.Red.ToArgb()));
                data.Write(new CustomVertex.PositionColored(0.0f, 0.0f, 0.0f, Color.Green.ToArgb()));
                data.Write(new CustomVertex.PositionColored(0.0f, 10.0f, 0.0f, Color.Green.ToArgb()));
                data.Write(new CustomVertex.PositionColored(0.0f, 0.0f, 0.0f, Color.Blue.ToArgb()));
                data.Write(new CustomVertex.PositionColored(0.0f, 0.0f, 10.0f, Color.Blue.ToArgb()));

                this._xyzLineVertexBuffer.Unlock();
            }
        }

        /// <summary>
        /// カメラの位置をセット
        /// </summary>
        /// <param name="radius">半径(degree)</param>
        /// <param name="theta">水平方向角度(degree)</param>
        /// <param name="phi">垂直方向角度(degree)</param>
        private void SetCameraPosition(float radius, float theta, float phi)
        {
            this._lensPosRadius = radius;
            this._lensPosTheta = theta;
            this._lensPosPhi = phi;
        }

        /// <summary>
        /// ライトの設定
        /// </summary>
        private void SettingLight()
        {
            // 平行光線を使用
            this._device.Lights[0].Type = LightType.Directional;

            // ライトの方向
            this._device.Lights[0].Direction = new Vector3(1.0f, -1.5f, 2.0f);

            // 光の色は白
            this._device.Lights[0].Diffuse = Color.White;

            // 環境光
            this._device.Lights[0].Ambient = Color.FromArgb(255, 128, 128, 128);

            // 0 番のライトを有効
            this._device.Lights[0].Enabled = true;

            // 0 番のライトを更新
            this._device.Lights[0].Update();
        }

        /// <summary>
        /// カメラの設定
        /// </summary>
        private void SettingCamera()
        {
            // レンズの位置を三次元極座標で変換
            float radius = this._lensPosRadius;
            float theta = Geometry.DegreeToRadian(this._lensPosTheta);
            float phi = Geometry.DegreeToRadian(this._lensPosPhi);
            Vector3 lensPosition = new Vector3(
                (float)(radius * Math.Cos(theta) * Math.Cos(phi)),
                (float)(radius * Math.Sin(phi)),
                (float)(radius * Math.Sin(theta) * Math.Cos(phi)));

            // ビュー変換行列を設定
            this._device.Transform.View = Matrix.LookAtLH(
                lensPosition, new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f));

            // 射影変換を設定
            this._device.Transform.Projection = Matrix.PerspectiveFovLH(
                Geometry.DegreeToRadian(60.0f),
                (float)this._device.Viewport.Width / (float)this._device.Viewport.Height,
                1.0f, 100.0f);
        }

        /// <summary>
        /// デバイスを保証する
        /// </summary>
        /// <returns></returns>
        private bool EnsureDevice()
        {
            // デバイスが動作可能かチェック
            int deviceResult;
            if (!this._device.CheckCooperativeLevel(out deviceResult))
            {
                switch ((ResultCode)deviceResult)
                {
                    case ResultCode.DeviceLost:
                        // まだリセットできる状態ではないので少し待つ
                        System.Threading.Thread.Sleep(10);
                        return false;
                    case ResultCode.DeviceNotReset:
                        // リセット可能状態

                        // デバイスをリセット
                        this._device.Reset(this._device.PresentationParameters);
                        return true;
                    default:
                        // 原因不明(正確には上記以外)
                        // まだリセットできる状態ではないので少し待つ
                        System.Threading.Thread.Sleep(10);
                        return false;
                }
            }
            return true;
        }

        /// <summary>
        /// XYZライン描画
        /// </summary>
        private void RenderXYZLine()
        {
            // ライトの状態を記憶
            bool oldLightEnable = this._device.RenderState.Lighting;

            // ライトを無効
            if (oldLightEnable)
            {
                this._device.RenderState.Lighting = false;
            }

            // テクスチャー無効
            this._device.SetTexture(0, null);

            // 原点に配置
            this._device.SetTransform(TransformType.World, Matrix.Identity);

            // ストリームセット
            this._device.SetStreamSource(0, this._xyzLineVertexBuffer, 0);

            // 頂点フォーマットセット
            this._device.VertexFormat = CustomVertex.PositionColored.Format;

            // 描画
            this._device.DrawPrimitives(PrimitiveType.LineList, 0, 3);

            // ライトを戻す
            if (oldLightEnable)
            {
                this._device.RenderState.Lighting = oldLightEnable;
            }
        }

        /// <summary>
        /// テキストの描画
        /// </summary>
        /// <param name="text">描画する文字列</param>
        /// <param name="left">文字列の左位置</param>
        /// <param name="top">文字列の上位置</param>
        /// <param name="color">文字の色</param>
        private void DrawText(string text, int left, int top, Color color)
        {
            this._font.DrawText(null, text, left, top, color);
        }

        /// <summary>
        /// リソースの破棄
        /// </summary>
        private void DisposeResource()
        {
            // XYZラインの破棄
            this.SafeDispose(this._xyzLineVertexBuffer);

            // フォントのリソース解放
            this.SafeDispose(this._font);

            // Direct3D デバイスのリソース解放
            this.SafeDispose(this._device);
        }

        /// <summary>
        /// 安全なリソース破棄
        /// </summary>
        /// <param name="resource">破棄するリソース</param>
        private void SafeDispose(IDisposable resource)
        {
            if (resource != null)
            {
                resource.Dispose();
            }
        }
    }
}
Box.cs ファイルのコードはこちらです。

Box.cs

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace MDXSample
{
    /// <summary>
    /// ボックスメッシュ
    /// </summary>
    class Box : IDisposable
    {
        /// <summary>
        /// Direct3D デバイス
        /// </summary>
        private Device _device = null;

        /// <summary>
        /// 頂点バッファ
        /// </summary>
        private VertexBuffer _vertexBuffer = null;

        /// <summary>
        /// インデックスバッファ
        /// </summary>
        private IndexBuffer _indexBuffer = null;

        /// <summary>
        /// インデックスバッファの各頂点番号配列
        /// </summary>
        private static Int16[] _vertexIndices = new Int16[] { 2, 0, 1, 1, 3, 2, 4, 0, 2, 2, 6, 4,
            5, 1, 0, 0, 4, 5, 7, 3, 1, 1, 5, 7, 6, 2, 3, 3, 7, 6, 4, 6, 7, 7, 5, 4 };


        /// <summary>
        /// コンストラクタ
        /// </summary>
        public Box()
        {
        }

        /// <summary>
        /// リソースの破棄
        /// </summary>
        public void Dispose()
        {
            if (this._vertexBuffer != null)
            {
                this._vertexBuffer.Dispose();
            }
            if (this._indexBuffer != null)
            {
                this._indexBuffer.Dispose();
            }
        }

        /// <summary>
        /// ボックスの作成
        /// </summary>
        /// <param name="device">Direct3D デバイス</param>
        /// <param name="min">ボックスの最小位置</param>
        /// <param name="max">ボックスの最大位置</param>
        /// <returns>作成したボックスインスタンス</returns>
        public static Box Create(Device device, Vector3 min, Vector3 max)
        {
            Box b = new Box();

            // デバイスセット
            b._device = device;

            // 箱を作成するための頂点バッファを作成
            // 箱の頂点は8個
            b._vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionNormal),
                8, device, Usage.None, CustomVertex.PositionNormal.Format, Pool.Managed);

            // 8点の情報を格納するためのメモリを確保
            CustomVertex.PositionNormal[] vertices = new CustomVertex.PositionNormal[8];

            // 各頂点を設定
            vertices[0] = new CustomVertex.PositionNormal(new Vector3(min.X, max.Y, max.Z),
                Vector3.Normalize(new Vector3(-1.0f, 1.0f, 1.0f)));
            vertices[1] = new CustomVertex.PositionNormal(new Vector3(max.X, max.Y, max.Z),
                Vector3.Normalize(new Vector3(1.0f, 1.0f, 1.0f)));
            vertices[2] = new CustomVertex.PositionNormal(new Vector3(min.X, max.Y, min.Z),
                Vector3.Normalize(new Vector3(-1.0f, 1.0f, -1.0f)));
            vertices[3] = new CustomVertex.PositionNormal(new Vector3(max.X, max.Y, min.Z),
                Vector3.Normalize(new Vector3(1.0f, 1.0f, -1.0f)));
            vertices[4] = new CustomVertex.PositionNormal(new Vector3(min.X, min.Y, max.Z),
                Vector3.Normalize(new Vector3(-1.0f, -1.0f, 1.0f)));
            vertices[5] = new CustomVertex.PositionNormal(new Vector3(max.X, min.Y, max.Z),
                Vector3.Normalize(new Vector3(1.0f, -1.0f, 1.0f)));
            vertices[6] = new CustomVertex.PositionNormal(new Vector3(min.X, min.Y, min.Z),
                Vector3.Normalize(new Vector3(-1.0f, -1.0f, -1.0f)));
            vertices[7] = new CustomVertex.PositionNormal(new Vector3(max.X, min.Y, min.Z),
                Vector3.Normalize(new Vector3(1.0f, -1.0f, -1.0f)));

            // 頂点バッファをロックする
            using (GraphicsStream data = b._vertexBuffer.Lock(0, 0, LockFlags.None))
            {
                // 頂点データを頂点バッファにコピーします
                data.Write(vertices);

                // 頂点バッファのロックを解除します
                b._vertexBuffer.Unlock();

                data.Close();
            }

            // インデックスバッファの作成
            // 第2引数の数値は(三角ポリゴンの数)*(ひとつの三角ポリゴンの頂点数)*
            // (16 ビットのインデックスサイズ(2byte))
            b._indexBuffer = new IndexBuffer(device, 12 * 3 * 2, Usage.WriteOnly,
                Pool.Managed, true);

            // インデックスバッファをロックする
            using (GraphicsStream data = b._indexBuffer.Lock(0, 0, LockFlags.None))
            {
                // インデックスデータをインデックスバッファにコピーします
                data.Write(_vertexIndices);

                // インデックスバッファのロックを解除します
                b._indexBuffer.Unlock();

                data.Close();
            }

            return b;
        }

        /// <summary>
        /// 描画
        /// </summary>
        public void Draw()
        {
            if (this._device == null || this._indexBuffer == null || this._vertexBuffer == null)
            {
                return;
            }

            // 頂点バッファをデバイスのデータストリームにバインド
            this._device.SetStreamSource(0, this._vertexBuffer, 0);

            // 描画する頂点のフォーマットをセット
            this._device.VertexFormat = CustomVertex.PositionNormal.Format;

            // インデックスバッファをセット
            this._device.Indices = this._indexBuffer;

            // レンダリング(描画)
            this._device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);
        }
    }
}

/// <summary>
/// ティーポット
/// </summary>
private Mesh _teapot = null;

/// <summary>
/// ティーポット当たり判定ボックスの最小値
/// </summary>
private Vector3 _teapotBoxMin = Vector3.Empty;

/// <summary>
/// ティーポット当たり判定ボックスの最大値
/// </summary>
private Vector3 _teapotBoxMax = Vector3.Empty;

/// <summary>
/// ティーポットの当たり判定球実体化用
/// </summary>
private Box _teapotBox = null;

/// <summary>
/// ティーポットの位置
/// </summary>
private Vector3 _teapotPosition = Vector3.Empty;

今回はボックス型の衝突判定を行うので、軸並行ボックスの大きさを持つようにします。ボックスの大きさは下の図のように2つのベクトルで定義できます。

ボックスの大きさ

包括ボックスを視覚的に描画するために「Box」というクラスを使用していますが、これはサンプルオリジナルのクラスです。基本的にはボックスメッシュを作成して描画するという単純なクラスになっており、今まで登場した Tips の内容で構成されています。詳しくはソースコードを見てください。


/// <summary>
///
/// </summary>
private Mesh _sphere = null;

/// <summary>
/// 球当たり判定ボックスの最小値
/// </summary>
private Vector3 _sphereBoxMin = Vector3.Empty;

/// <summary>
/// 球当たり判定ボックスの最大値
/// </summary>
private Vector3 _sphereBoxMax = Vector3.Empty;

/// <summary>
/// 球の当たり判定球実体化用
/// </summary>
private Box _sphereBox = null;

/// <summary>
/// 球の位置
/// </summary>
private Vector3 _spherePosition = new Vector33(-2.0f, 0.0f, -3.0f);

ティーポットと同じようなデータ定義をしています。


/// <summary>
/// 当たり判定フラグ
/// </summary>
private bool _hitFlag = false;

衝突しているかを文字で描画するために、衝突フラグを持っておきます。


// ティーポット作成
this._teapot = Mesh.Teapot(this._device);

ティーポットメッシュを作成しています。


// ティーポットの包括球を計算するために頂点バッファ取得
using (VertexBuffer vb = this._teapot.VertexBuffer)
{
    // 頂点バッファをロックしてストリーム取得
    using (GraphicsStream vertexData = vb.Lock(0, 0, LockFlags.None))
    {
        // ティーポットの包括ボックスを計算し、大きさ取得
        Geometry.ComputeBoundingBox(vertexData,
            this._teapot.NumberVertices, this._teapot.VertexFormat,
            out this._teapotBoxMin, out this._teapotBoxMax);

        vertexData.Close();
    }

    // 頂点ロック解除
    vb.Unlock();
}

「球と球の衝突判定」と同様に、今回はメッシュを包括するボックスの大きさを取得します。

ボックスの大きさは「Geometry.ComputeBoundingBox」メソッドで取得でき、第4引数にボックスの最小位置、第5引数にボックスの最大位置を渡して受け取ります。


// 見やすいように当たり判定ボックスを作成
this._teapotBox = Box.Create(this._device, this._teapotBoxMin, this._teapotBoxMax);

Box.Create」静的メソッドにボックスの大きさを指定することでボックスオブジェクトを作成できます。第2引数にボックスの最小位置、第3引数にボックスの最大位置を指定してください。


// 球作成
this._sphere = Mesh.Sphere(this._device, 1.5f, 16, 16);

// 球の包括ボックスを計算し、大きさ取得
using (VertexBuffer vb = this._sphere.VertexBuffer)
{
    // 頂点バッファをロックしてストリーム取得
    using (GraphicsStream vertexData = vb.Lock(0, 0, LockFlags.None))
    {
        // 球の包括ボックスを計算し、大きさ取得
        Geometry.ComputeBoundingBox(vertexData,
            this._sphere.NumberVertices, this._sphere.VertexFormat,
            out this._sphereBoxMin, out this._sphereBoxMax);

        vertexData.Close();
    }

    // 頂点ロック解除
    vb.Unlock();
}

// 見やすいように当たり判定ボックスを作成
this._sphereBox = Box.Create(this._device, this._sphereBoxMin, this._sphereBoxMax);

球に関しても、ティーポットと同じように作成します。


// ワールド座標でのボックスの面の位置を計算しておく
Vector3 worldTeapotMin = this._teapotPosition + this._teapotBoxMin;
Vector3 worldTeapotMax = this._teapotPosition + this._teapotBoxMax;
Vector3 worldSphereMin = this._spherePosition + this._sphereBoxMin;
Vector3 worldSphereMax = this._spherePosition + this._sphereBoxMax;

// 各6つの面の位置関係を調べる
if (worldTeapotMin.X < worldSphereMax.X &&
    worldTeapotMin.Y < worldSphereMax.Y &&
    worldTeapotMin.Z < worldSphereMax.Z &&
    worldTeapotMax.X > worldSphereMin.X &&
    worldTeapotMax.Y > worldSphereMin.Y &&
    worldTeapotMax.Z > worldSphereMin.Z)
{
    this._hitFlag = true;
}
else
{
    this._hitFlag = false;
}

今回の Tips のメインであるボックスとボックスの当たり判定処理です。

まず最初に衝突判定ボックスをワールド座標で計算するので、モデルの現在位置を加算しておきます。

その値を各6つの面の位置関係を調べて、すべて当てはまっていれば衝突していることになります。

コードだけ見るとやや分かりづらいので下の図を見てください。ボックスを真上から見ている図なので X と Z しか書いていませんが、Y に関しても同じになります。

ボックス同士の当たり判定
ようは A, B の min の値が全て、もう片方の max よりも小さければ当たっていることになる


Material material;


// 不透明マテリアル使用
material = new Material();
material.Diffuse = Color.White;
material.Ambient = Color.FromArgb(255, 128, 128, 128);
this._device.Material = material;

// アルファブレンディングを無効にする
this._device.SetRenderState(RenderStates.AlphaBlendEnable, false);

// ティーポット描画
this._device.SetTransform(TransformType.World, Matrix.Translation(this._teapotPosition));
this._teapot.DrawSubset(0);

// 球描画
this._device.SetTransform(TransformType.World, Matrix.Translation(this._spherePosition));
this._sphere.DrawSubset(0);


// 半透明マテリアル使用
material = new Material();
material.Diffuse = Color.FromArgb(128, 192, 192, 192);
material.Ambient = Color.FromArgb(128, 0, 0, 128);
this._device.Material = material;

// アルファブレンディングを有効にする
this._device.SetRenderState(RenderStates.AlphaBlendEnable, truee);

// ティーポット包括ボックス描画
this._device.SetTransform(TransformType.World, Matrix.Translation(this._teapotPosition));
this._teapotBox.Draw();

// 球包括ボックス描画
this._device.SetTransform(TransformType.World, Matrix.Translation(this._spherePosition));
this._sphereBox.Draw();

描画に関しては特に変わった部分はありません。

Box クラスは「Box.Draw」メソッドで描画することが出来ます。