衝突判定実装のログ

真面目にやると時間がかかる。球と直方体の衝突は辺や角への激突がすごく面倒。
あと、3次元座標の計算をち一通りできるライブラリを探した方が良さそう。PV3Dに付いてるやつだとベクトルのスケーリングができないのが不便。内積外積の取り方は個人差があるだろうけど、自分としては「vecC = vecA.cross(vecB)」と書きたい。「vecC = Vector.cross(vecA, vecB)」は長くなるのがイヤ。できればスケーリングも「vecB = scalar * vecA」みたいに書きたいけど、AS3だとたぶん無理なんだよね。
あと、今のコードだと何故かNumber3Dの宣言を見つけられてない。「packageの外のクラス用のimportはpackageの外でやっておくっぽい」というのは思い出したのだが、それでも上手くいかん。そこらへんの調査も必要。どうしようもなければ内部クラスにするか。


ひとまず今のコードは下の通り(コンパイルは通らない)。本当に土日で完成するんだろうか。


//mxmlc Main.as
//author Show=O=Healer

package {
	//Common
	import flash.display.*;
	import flash.events.*;

	//Class
	public class Main extends Sprite {
		//Common
		private var m_Container	: Sprite;

		//Param
		private const GRAVITY:Number = 10.0;


		//Constructor
		public function Main():void {
			{//Init Common
				//リサイズ対応
				stage.addEventListener(Event.RESIZE, onStageResize);

				//定期的にupdateを呼ばせる
				addEventListener(Event.ENTER_FRAME, update);
			}

			addChild(new BlockManager());
		}


		//=Common=

		private function update( event:Event ):void {
		}

		private function onStageResize(event:Event):void {
			m_Container.x = stage.stageWidth	/ 2;
			m_Container.y = stage.stageHeight / 2;
		}
	}
}


//=Local Class=

import flash.display.*;

//PV3D
import org.papervision3d.core.Number3D;


class CollisionSphere
{
	public var m_CollisionPos:Number3D;
	public var m_CollisionVel:Number3D = new Number3D(0.0, 0.0, 0.0);
	public var m_CollisionAcc:Number3D = new Number3D(0.0, 0.0, 0.0);

	public var m_CollisionRad:Number;//半径

	public function CollisionSphere(in_X:Number, in_Y:Number, in_R:Number):void {
		m_CollisionPos = new Number3D(in_X, in_Y, 0.0);

		m_CollisionRad = in_R;
	}
}

class CollisionBox
{
	public var m_CollisionPos:Number3D;
	public var m_CollisionVel:Number3D = new Number3D(0.0, 0.0, 0.0);
	public var m_CollisionAcc:Number3D = new Number3D(0.0, 0.0, 0.0);

	public var m_CollisionW:Number;//幅(端から端まで)
	public var m_CollisionH:Number;//高さ(端から端まで)

	public function CollisionBox(in_X:Number, in_Y:Number, in_W:Number, in_H:Number):void {
		m_CollisionPos = new Number3D(in_X, in_Y, 0.0);

		m_CollisionW = in_W;
		m_CollisionH = in_H;
	}
}


var BLOCK_DATA:Array = [
	[0, 0, 0, 0, 0],
	[0, 0, 0, 1, 0],
	[0, 0, 0, 1, 0],
	[0, 0, 0, 1, 1],
	[0, 1, 0, 1, 1],
	[0, 1, 1, 1, 1],
	[1, 1, 1, 1, 1]
];


//BLOCK_DATAをもとにBlockを生成
class BlockManager extends Sprite
{
	private var m_PlayerSphere:CollisionSphere = new CollisionSphere(5.0, 5.0, 4.0);
	private var m_BlockCollisionList:Array = new Array();

	//Param
	private const W:int = 10;
	private const H:int = 10;

	public function BlockManager():void {
		BLOCK_DATA.forEach(
			function(innerArray:Array, indexY:int, array:Array):void{
				innerArray.forEach(
					function(val:int, indexX:int, array2:Array):void{
						if(val == 1){
							CreateBlock(indexX, indexY);
							m_BlockCollisionList.push(new CollisionBox(0.5*W + W*indexX, 0.5*H + H*indexY, W, H));
						}
					}
				);
			}
		);
	}

	//指定箇所にブロックを作成
	public function CreateBlock(x:int, y:int):void{
		var block:Sprite = new Sprite();
		block.graphics.lineStyle(1, 0x4b2503, 1.0);
		block.graphics.beginFill(0x8b4513);
		block.graphics.drawRect(-0.5*W, -0.5*H, W, H);
		block.graphics.endFill();
		block.x = 0.5*W + W * x;
		block.y = 0.5*H + H * y;
		addChild(block);
	}

	//
	public function UpdateCollision():void{
		var deltaTime:Number = 1.0/60.0;

		{
			var sphere:CollisionSphere = m_PlayerSphere;
			var sphereRad:Number = sphere.m_CollisionRad;

			var restTime = deltaTime;

			var nowPos:Number3D = sphere.m_CollisionPos;
			var nowVel:Number3D = sphere.m_CollisionVel;
			var nowAcc:Number3D = sphere.m_CollisionAcc;


			for(;;)//continueでここまで戻ってきてもう一度処理する
			{
				//ここのループにカウンタを仕込めば、「N回だけ跳ね返りを考慮する」というのができる。
				//ただ、その場合はすり抜けるかもしれない

				var nextPos:Number3D = nowPos + nowVel * restTime + 0.5 * nowAcc * restTime * restTime;
				var nextVel:Number3D = nowVel + nowAcc * restTime;

				var hitBlock:CollisionBlock = null;
				var hitTime:Number = restTime;
				var hitPos:Number3D;
				var hitVel:Number3D;
				m_BlockCollisionList.forEach(
					function(blockCollision:CollisionBox, index:int, arr:Array):void{
						//=ブロックと球の相対的な移動量を求める(ブロックをベースとする)=

						//ブロックにとっての前後上下左右の軸
						var axisX:Number3D = new Number3D(1.0, 0.0, 0.0);
						var axisY:Number3D = new Number3D(0.0, 1.0, 0.0);
//						var axisZ:Number3D = new Number3D(0.0, 0.0, 1.0);

						//ブロックの位置
						var blockPos:Number3D = blockCollision.m_CollisionPos;

						//ブロックと球の相対位置
						var nowGap:Number3D = Number3D.sub(nowPos, blockPos);
						var nextGap:Number3D = Number3D.sub(nextPos, blockPos);


						//各軸における相対差
						var nowGapX:Number = Number3D.dot(axisX, nowGap);
						var nowGapY:Number = Number3D.dot(axisY, nowGap);
//						var nowGapZ:Number = Number3D.dot(axisZ, nowGap);
						var nextGapX:Number = Number3D.dot(axisX, nextGap);
						var nextGapY:Number = Number3D.dot(axisY, nextGap);
//						var nextGapZ:Number = Number3D.dot(axisZ, nextGap);


						//=衝突判定=

						//完全に範囲外となるなら、ここでヒットしないと判定して終了
						if(nowGapX < -0.5*W - sphereRad && nextGapX < -0.5*W - sphereRad){return;}
						if(nowGapX >  0.5*W + sphereRad && nextGapX >  0.5*W + sphereRad){return;}
						if(nowGapY < -0.5*H - sphereRad && nextGapY < -0.5*H - sphereRad){return;}
						if(nowGapY >  0.5*H + sphereRad && nextGapY >  0.5*H + sphereRad){return;}
//						if(nowGapZ < -0.5*D - sphereRad && nextGapZ < -0.5*D - sphereRad){return;}
//						if(nowGapZ >  0.5*D + sphereRad && nextGapZ >  0.5*D + sphereRad){return;}

						//ブロックの各面に対して衝突判定を行う
						var hitCheck:Function = function(
							axisA:Number3D,
							axisB:Number3D,
//							axisC:Number3D,
							widthA:Number,
							widthB:Number
//							widthC:Number
						):void{
							var movePos:Number3D = Number3D.sub(nextPos - nowPos);

							var hitCheckPlane:Function = function(
								axis:Number3D
							):void{
								if(Number3D.Dot(axis, Number3D.sub(nextPos - nowPos)) >= 0.0){return;}
								var scale:Number = (Number3D.dot(axis, Number3D.sub(nowPos, blockPos)) - (0.5*widthA + sphereRad))/Number3D.dot(axis, Number3D.sub(nowPos, nextPos));
								var contactPos:Number3D = Number3D.add(nowPos, new Number3D(movePos.x * scale, movePos.y * scale, movePos.z * scale));

								//端ではなく中央に当たるなら、普通に処理
								if(-widthB <= Number3D.dot(contactPos, axisB) && Number3D.dot(contactPos, axisB) <= widthB){
									var localHitPos:Number3D = contactPos;

									//全体の移動量と実際の移動量の比から、その移動にかかった時間を求める
									var ratio:Number = Number3D.sub(localHitPos, nowPos).modulo() / movePos.modulo();

									//厳密には、この移動にかかった時間を距離から逆算して速度などを割り出すべき
									//今回は、面倒なので近似で済ます

									var localHitTime:Number = restTime * ratio;//衝突までにかかった時間:逆算して出したい
									var bounceRatio:Number = 0.0;//axis方向の速度を相殺するだけでバウンドしない
									var localVel:Number3D = new Number3D(nowVel.x * (1.0 - ratio) + nextVel.x * ratio, nowVel.y * (1.0 - ratio) + nextVel.y * ratio, nowVel.z * (1.0 - ratio) + nextVel.z * ratio);//ヒット時点での速度:逆算して出したい
									var localRelV:Number = -(1.0 + bounceRatio) * Number3D.dot(axisA, localVel);//下で使う速度の増減量
									var localHitVel:Number = Number3D.sub(nowVel, new Number3d(axisA.x * localRelV, axisA.y * localRelV, axisA.z * localRelV));//跳ね返り後の速度

									if(localHitTime < hitTime){
										hitTime = localHitTime;
										hitBlock = blockCollision;
										hitPos = localHitPos;
										hitVel = localHitVel;
									}

									return;
								}

								//端に当たるなら、円柱や球との当たり判定をもう一度計算して、それらのうち最も早く当たるものを採用
								//!!
							};

							var minusAxisA:Number3D = new Number3D(-axisA.x, -axisA.y, -axisA.z);

							hitCheckPlane(axisA);
							hitCheckPlane(minusAxisA);
						};


//						hitCheck(axisX, axisY, axisZ, W, H, D);
//						hitCheck(axisY, axisZ, axisX, H, D, W);
//						hitCheck(axisZ, axisX, axisY, D, W, H);
						hitCheck(axisX, axisY, W, H);
						hitCheck(axisY, axisX, H, W);
					}
				);

				//何かとぶつかっていたら、新しい位置と方向を元に再び処理を開始
				if(hitBlock != null){
					restTime -= hitTime;
					nowPos = hitPos;
					nowVel = hitVel;

					continue;
				}

				//ぶつかっていなければ、求めた移動先に移動して終了
				sphere.m_CollisionPos = nextPos;
				sphere.m_CollisionVel = nextVel;

				break;
			}
		}
	}
}