ボックスとボックスの衝突判定
衝突判定の中では比較的高速な判定が出来る方法です。回転しない箱型物体、例えばキャラクター以外の家とか置物とかにとても有効です。
操作はティーポットを「←」「→」「↑」「↓」キーで「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></returns>
/// <remarks>
///
/// </remarks>
public bool InitializeApplication(MainForm topLevelForm)
{
this._form = topLevelForm;
this.CreateInputEvent(topLevelForm);
try
{
this.CreateDevice(topLevelForm);
this.CreateFont();
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;
}
this._device.Clear(ClearFlags.ZBuffer | ClearFlags.Target, Color.DarkBlue, 1.0f, 0);
this._device.BeginScene();
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();
}
}
}
|
では、赤文字の部分を説明していきます。そのほかのファイルのコードはこちらです。
/// <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;
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」メソッドで描画することが出来ます。
|