球と面の衝突判定実装ログ
状況
線と円柱の衝突判定を組み込むも、上手く動かず。そこをオフにするととりあえず跳ねるので、平面の方は上手くいっているはず。やはり土日までかかる。
→少しいじって直した。ボールを動かして確認しないと大丈夫とは言えないが。土日で「プレイヤーを動かせるようにする:十字キーで前後左右に移動し、スペースキーでジャンプする」を組み込んでチェックする。
コード
そろそろ1000行を越えそうなので、そのまま転載というのはつらいかも。タブをそのままコピペしてるから枠をはみ出てるし。あとで整理するから、多少は縮まるだろうけど。
//mxmlc Main.as //author Show=O=Healer package { //Common import flash.display.*; import flash.events.*; //Debug import flash.text.*; import flash.ui.*; import flash.utils.*; //Input import flash.ui.Keyboard; //Class public class Main extends Sprite { //Block private var m_BlockManager:BlockManager; //Input private var m_InputL:int = 0; private var m_InputR:int = 0; private var m_InputU:int = 0; private var m_InputD:int = 0; //Param private const PLAYER_VEL:Number = 160.0; private const GRAVITY:Number = 0.0;//-1000.0; //Debug public static var m_Label:TextField; //Constructor public function Main():void { {//Init Block m_BlockManager = new BlockManager(); addChild(m_BlockManager); } {//Init Input stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); } {//Init Common //リサイズ対応 // stage.addEventListener(Event.RESIZE, m_BlockManager.onStageResize); //定期的にupdateを呼ばせる addEventListener(Event.ENTER_FRAME, update); } {//Init Debug m_Label = new TextField(); m_Label.autoSize = TextFieldAutoSize.LEFT; m_Label.selectable=false addChild(m_Label); } } //=Common= private function update( event:Event ):void { //プレイヤーの移動速度をセット // m_BlockManager.m_PlayerSphere.m_CollisionVel.x = PLAYER_VEL * (m_InputR - m_InputL); // if(m_InputU > 0){ // m_BlockManager.m_PlayerSphere.m_CollisionVel.y = -PLAYER_VEL; // } //コリジョンを元に位置更新 m_BlockManager.UpdateCollision(); //描画 m_BlockManager.Render(); //更新した位置を表示に反映 // m_Player.x = m_BlockManager.m_PlayerSphere.m_CollisionPos.x; // m_Player.y = m_BlockManager.m_PlayerSphere.m_CollisionPos.y; } //=Input= private function onKeyDown(event:KeyboardEvent):void{ if(event.keyCode == Keyboard.LEFT){ m_InputL = 1; } if(event.keyCode == Keyboard.RIGHT){ m_InputR = 1; } if(event.keyCode == Keyboard.UP){ m_InputU = 1; } if(event.keyCode == Keyboard.DOWN){ m_InputD = 1; } } private function onKeyUp(event:KeyboardEvent):void{ if(event.keyCode == Keyboard.LEFT){ m_InputL = 0; } if(event.keyCode == Keyboard.RIGHT){ m_InputR = 0; } if(event.keyCode == Keyboard.UP){ m_InputU = 0; } if(event.keyCode == Keyboard.DOWN){ m_InputD = 0; } } } } //=Local Class= //Common import flash.display.*; import flash.events.*; //Debug import flash.text.*; import flash.ui.*; import flash.utils.*; //Input import flash.ui.Keyboard; //PV3D import org.papervision3d.core.*; import org.papervision3d.core.proto.*; import org.papervision3d.scenes.*; import org.papervision3d.objects.*; import org.papervision3d.cameras.*; import org.papervision3d.materials.*; //=球体コリジョン= class CollisionSphere { //中心位置 public var m_CollisionPos:CVector; //移動速度 public var m_CollisionVel:CVector = new CVector(0, 0, 0); //加速度 public var m_CollisionAcc:CVector = new CVector(0, 0, 0); //半径 public var m_CollisionRad:Number; //コンストラクタ public function CollisionSphere(in_Pos:CVector, in_R:Number):void { m_CollisionPos = in_Pos; m_CollisionRad = in_R; } } //=面コリジョン= class CollisionPlane { //中心位置 public var m_CollisionPos:CVector; // public var m_CollisionVel:CVector = new CVector(0, 0, 0); // public var m_CollisionAcc:CVector = new CVector(0, 0, 0); //縦の方向 public var m_AxisA:CVector; //縦の長さ(端から端まで) public var m_WidthA:Number; //横の方向 public var m_AxisB:CVector; //横の長さ(端から端まで) public var m_WidthB:Number; //コンストラクタ public function CollisionPlane(in_Pos:CVector, in_AxisA:CVector, in_WidthA:Number, in_AxisB:CVector, in_WidthB:Number):void { m_CollisionPos = in_Pos; m_AxisA = in_AxisA; m_WidthA = in_WidthA; m_AxisB = in_AxisB; m_WidthB = in_WidthB; } } //=3次元ベクトル= class CVector { //メンバ public var x:Number = 0; public var y:Number = 0; public var z:Number = 0; //コンストラクタ public function CVector(in_x:Number = 0, in_y:Number = 0, in_z:Number = 0):void{ x = in_x; y = in_y; z = in_z; } //長さ public function Length():Number{ return Math.sqrt(x*x + y*y + z*z); } //=各種オペレータ= //本体には影響を与えない //vecA + vecB の代わりに vecA.Plus(vecB) とやる感じ //あるいは vecA.Dot(vecB) の延長で vecA.Plus(vecB) がある感じ //和 public function Plus(rhs:CVector):CVector{ return new CVector(x + rhs.x, y + rhs.y, z + rhs.z); } //差 public function Minus(rhs:CVector):CVector{ return new CVector(x - rhs.x, y - rhs.y, z - rhs.z); } //内積 public function Dot(rhs:CVector):Number{ return x * rhs.x + y * rhs.y + z * rhs.z; } //外積 public function Cross(rhs:CVector):CVector{ return new CVector(y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x); } //スケール public function Scale(scl:Number):CVector{ return new CVector(x * scl, y * scl, z * scl); } //正規化 public function Normal():CVector{ var distance:Number = Length(); if(distance > 0.0){ return new CVector(x/distance, y/distance, z/distance); } return new CVector(0, 0, 0);//err } //線形補間 public function Lerp(rhs:CVector, ratio:Number):CVector{ //this * (1 - ratio) + rhs * ratio return Scale(1.0 - ratio).Plus(rhs.Scale(ratio)); } //値の比較 public function Equals(rhs:CVector):Boolean{ return (x == rhs.x) && (y == rhs.y) && (z == rhs.z); } } //=ブロックの配置= var BLOCK_DATA:Array = [ [ [0, 0, 1, 0, 0], [0, 1, 1, 1, 0], [1, 1, 1, 1, 1], [0, 1, 1, 1, 0], [0, 0, 1, 0, 0], ], [ [0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], ], /* [ [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], ], //*/ ]; //=ブロックの管理と衝突管理(できれば分けたい)= class BlockManager extends Sprite { //Common private var m_Container : Sprite; //PV3D private var m_Scene : Scene3D; private var m_Camera : Camera3D; // private var m_Camera : FreeCamera3D; private var m_RootNode : DisplayObject3D; // private var m_BoundArea : DisplayObject3D; private var m_Sphere : DisplayObject3D; private var m_Cube : DisplayObject3D; //=CollisionSphere= //プレイヤの球コリジョン(できればもっと自由に登録できるようにしたい) public var m_PlayerSphere:CollisionSphere = new CollisionSphere(new CVector(SPHERE_INIT_X, SPHERE_INIT_Y, SPHERE_INIT_Z), SPHERE_RAD); //=CollisionPlane= //面コリジョン private var m_PlaneCollisionList:Array = new Array(); //Param private const BOUND_AREA_W:Number = 600; private const BOUND_AREA_H:Number = 200; private const BOUND_AREA_D:Number = 500; private const SPHERE_INIT_X:Number = 0; private const SPHERE_INIT_Y:Number = 50;//10; private const SPHERE_INIT_Z:Number = 0;//20; private const SPHERE_RAD:Number = 8; private const CAMERA_DISTANCE:Number = 400; private const GRAVITY:Number = -100.0; private const AXIS_X:CVector = new CVector(1, 0, 0); private const AXIS_Y:CVector = new CVector(0, 1, 0); private const AXIS_Z:CVector = new CVector(0, 0, 1); //ブロックの幅 private const W:int = 20; private const H:int = 20; private const D:int = 20; //Constructor public function BlockManager():void { {//Init PV3D {//m_Container // 表示用の Sprite オブジェクトを生成 m_Container = new Sprite(); m_Container.x = 400 / 2; // at center : swf width = 400 m_Container.y = 400 / 2; // at center : swf height = 400 addChild(m_Container); } {//m_Scene // シーンオブジェクトを作る m_Scene = new Scene3D(m_Container); } {//m_Camera // カメラオブジェクトを作る m_Camera = new Camera3D(); // m_Camera = new FreeCamera3D(); m_Camera.x = 10; m_Camera.y = 20; m_Camera.z = -50; m_Camera.focus = 500; m_Camera.zoom = 1; } {//m_RootNode // ルートノードを作る m_RootNode = new DisplayObject3D(); m_Scene.addChild(m_RootNode); } {//m_Sphere m_Sphere = function():DisplayObject3D { var material:ColorMaterial = new ColorMaterial(0x000000); var sphere:DisplayObject3D = new Sphere(material, SPHERE_RAD); sphere.x = SPHERE_INIT_X; sphere.y = SPHERE_INIT_Y; sphere.z = SPHERE_INIT_Z; return sphere; }(); m_RootNode.addChild(m_Sphere); } {//m_PlayerSphere m_PlayerSphere.m_CollisionAcc.y = GRAVITY; } //ブロックの描画の登録と、その境界線上にコリジョン配置 //配列の中心が原点に来るようにする var centerPos:CVector = new CVector(0.5*W * BLOCK_DATA[0][0].length, 0.5*H * BLOCK_DATA.length, 0.5*D * BLOCK_DATA[0].length); BLOCK_DATA.forEach( function(innerArray:Array, indexY:int, array:Array):void{ innerArray.forEach( function(innerArray2:Array, indexZ:int, array:Array):void{ innerArray2.forEach( function(val:int, indexX:int, array2:Array):void{ //境界線上に描画面とコリジョンを配置(端が抜けているが、今回は気にしない) var pos:CVector; /* var val0:int; var val1:int; {//X {//0:1 〜 NUM-1:NUM val0 = val; if(indexX == BLOCK_DATA[0][0].length-1){ val1 = 0; }else{ val1 = BLOCK_DATA[indexY][indexZ][indexX+1]; } if(val0 != val1){ createPlane(indexX, indexX, indexZ, TYPE_X, val0); } } {//-1:0 if(indexX == 0){ if(0 != val){ createPlane(indexX, indexX, indexZ, TYPE_X, 0); } } } } //*/ if(indexX > 0){ if(BLOCK_DATA[indexY][indexZ][indexX-1] != val){ pos = (new CVector(W * (indexX), H * (indexY+0.5), D * (indexZ+0.5))).Minus(centerPos); m_PlaneCollisionList.push(new CollisionPlane(pos, AXIS_Y, H, AXIS_Z, D)); //描画用の面 m_RootNode.addChild( function():DisplayObject3D { var obj:DisplayObject3D = new Plane(new ColorMaterial(0xCCCCFF), D, H, 8); obj.x = pos.x; obj.y = pos.y; obj.z = pos.z; if(val == 1){ obj.rotationY = 90; }else{ obj.rotationY = -90; } return obj; }() ); } if(indexX == BLOCK_DATA[0][0].length-1){ if(val == 1){ pos = (new CVector(W * (indexX+1), H * (indexY+0.5), D * (indexZ+0.5))).Minus(centerPos); m_PlaneCollisionList.push(new CollisionPlane(pos, AXIS_Y, H, AXIS_Z, D)); //描画用の面 m_RootNode.addChild( function():DisplayObject3D { var obj:DisplayObject3D = new Plane(new ColorMaterial(0xCCCCFF), D, H, 8); obj.x = pos.x; obj.y = pos.y; obj.z = pos.z; obj.rotationY = -90; return obj; }() ); } } }else{ if(val == 1){ pos = (new CVector(W * (indexX), H * (indexY+0.5), D * (indexZ+0.5))).Minus(centerPos); m_PlaneCollisionList.push(new CollisionPlane(pos, AXIS_Y, H, AXIS_Z, D)); //描画用の面 m_RootNode.addChild( function():DisplayObject3D { var obj:DisplayObject3D = new Plane(new ColorMaterial(0xCCCCFF), D, H, 8); obj.x = pos.x; obj.y = pos.y; obj.z = pos.z; obj.rotationY = 90; return obj; }() ); } } if(indexY > 0){ if(BLOCK_DATA[indexY-1][indexZ][indexX] != val){ pos = (new CVector(W * (indexX+0.5), H * (indexY), D * (indexZ+0.5))).Minus(centerPos); m_PlaneCollisionList.push(new CollisionPlane(pos, AXIS_X, W, AXIS_Z, D)); //描画用の面 m_RootNode.addChild( function():DisplayObject3D { var obj:DisplayObject3D = new Plane(new ColorMaterial(0xCCFFCC), W, D, 8); obj.x = pos.x; obj.y = pos.y; obj.z = pos.z; if(val == 1){ obj.rotationX = 90; }else{ obj.rotationX = -90; } return obj; }() ); } if(indexY == BLOCK_DATA.length-1){ if(val == 1){ pos = (new CVector(W * (indexX+0.5), H * (indexY+1), D * (indexZ+0.5))).Minus(centerPos); m_PlaneCollisionList.push(new CollisionPlane(pos, AXIS_X, W, AXIS_Z, D)); //描画用の面 m_RootNode.addChild( function():DisplayObject3D { var obj:DisplayObject3D = new Plane(new ColorMaterial(0xCCFFCC), W, D, 8); obj.x = pos.x; obj.y = pos.y; obj.z = pos.z; obj.rotationX = -90; return obj; }() ); } } }else{ if(val == 1){ pos = (new CVector(W * (indexX+0.5), H * (indexY), D * (indexZ+0.5))).Minus(centerPos); m_PlaneCollisionList.push(new CollisionPlane(pos, AXIS_X, W, AXIS_Z, D)); //描画用の面 m_RootNode.addChild( function():DisplayObject3D { var obj:DisplayObject3D = new Plane(new ColorMaterial(0xCCFFCC), W, D, 8); obj.x = pos.x; obj.y = pos.y; obj.z = pos.z; obj.rotationX = 90; return obj; }() ); } } if(indexZ > 0){ if(BLOCK_DATA[indexY][indexZ-1][indexX] != val){ pos = (new CVector(W * (indexX+0.5), H * (indexY+0.5), D * (indexZ))).Minus(centerPos); m_PlaneCollisionList.push(new CollisionPlane(pos, AXIS_X, W, AXIS_Y, H)); //描画用の面 m_RootNode.addChild( function():DisplayObject3D { var obj:DisplayObject3D = new Plane(new ColorMaterial(0xFFCCCC), W, H, 8); obj.x = pos.x; obj.y = pos.y; obj.z = pos.z; if(val == 1){ }else{ obj.rotationX = 180; } return obj; }() ); } if(indexZ == BLOCK_DATA[0].length-1){ if(val == 1){ pos = (new CVector(W * (indexX+0.5), H * (indexY+0.5), D * (indexZ+1))).Minus(centerPos); m_PlaneCollisionList.push(new CollisionPlane(pos, AXIS_X, W, AXIS_Y, H)); //描画用の面 m_RootNode.addChild( function():DisplayObject3D { var obj:DisplayObject3D = new Plane(new ColorMaterial(0xFFCCCC), W, H, 8); obj.x = pos.x; obj.y = pos.y; obj.z = pos.z; obj.rotationX = -180; return obj; }() ); } } }else{ if(val == 1){ pos = (new CVector(W * (indexX+0.5), H * (indexY+0.5), D * (indexZ))).Minus(centerPos); m_PlaneCollisionList.push(new CollisionPlane(pos, AXIS_X, W, AXIS_Y, H)); //描画用の面 m_RootNode.addChild( function():DisplayObject3D { var obj:DisplayObject3D = new Plane(new ColorMaterial(0xFFCCCC), W, H, 8); obj.x = pos.x; obj.y = pos.y; obj.z = pos.z; // obj.rotationX = 180; return obj; }() ); } } } ); } ); } ); } } public function UpdateCollision():void{ //Debug Main.m_Label.text = ""; //この秒数経ったものとして計算 var deltaTime:Number = 1.0/60.0; {//各「球コリジョン」に対して計算(今は一つだけ) //球のパラメータ var sphere:CollisionSphere = m_PlayerSphere; var sphereRad:Number = sphere.m_CollisionRad; var sphereBoundRatio:Number = 1.0;//円側の反射率 //残りの処理時間(衝突のたびに、移動にかかった時間を引いていく) var restTime:Number = deltaTime; //現時点での位置など(衝突のたびに、その直後のパラメータをセット) var nowPos:CVector = sphere.m_CollisionPos; var nowVel:CVector = sphere.m_CollisionVel; var nowAcc:CVector = sphere.m_CollisionAcc; //衝突の計算をcountの許す限り行う for(var count:Number = 0; count < 5; count++)//continueでここまで戻ってきてもう一度処理する { //count以上の計算が必要な場合、すり抜ける可能性がある //ただ、これをなくすと無限ループに入る可能性がある //計算上の次の位置など //nextPos = nowPos + nowVel * restTime + 0.5 * nowAcc * restTime * restTime; //nextVel = nowVel + nowAcc * restTime; var nextPos:CVector = nowPos.Plus(nowVel.Scale(restTime)).Plus(nowAcc.Scale(0.5 * restTime * restTime)); var nextVel:CVector = nowVel.Plus(nowAcc.Scale(restTime)); //Main.m_Label.text = Main.m_Label.text + "\n" +nowVel.y + ":"+ nowAcc.y; //移動しようとする量 var movePos:CVector = nextPos.Minus(nowPos); //移動の長さ var moveDistance:Number = movePos.Length(); //ヒットしたら、その情報をこれに入れる var hitPlane:CollisionPlane= null; var hitTime:Number = restTime; var hitPos:CVector = nowPos; var hitVel:CVector; //各面に対して衝突判定 m_PlaneCollisionList.forEach( function(plane:CollisionPlane, index:int, arr:Array):void{ //球と面のヒットチェック //相対的に考えて、点と「丸角で厚みを持つ直方体」のヒットで考える //直方体の側を「前後の両面」「四隅の球」「それらをつなぐ4つの円柱」に分けて判定する var planeBoundRatio:Number = 0.5;//plane側の反射率 //=上下の面との衝突チェック= { var hitCheckPlane:Function = function(dir:Number):void{ var planeNrm:CVector = plane.m_AxisB.Cross(plane.m_AxisA).Scale(dir).Normal(); //面の中心位置 var pos:CVector = plane.m_CollisionPos.Plus(planeNrm.Scale(sphereRad)); //面の中心位置との相対位置 var nowGap:CVector = nowPos.Minus(pos); var nextGap:CVector = nextPos.Minus(pos); if(planeNrm.Dot(nowGap) * planeNrm.Dot(nextGap) <= 0.0){ //面をまたいで移動している if(planeNrm.Dot(movePos) <= 0.0){//こちらの面に向かってきている //線分と交差する場合の位置 var localHitPos:CVector; if(planeNrm.Dot(movePos) < 0.0){ //普通に向かってきている場合 //交点を求める //hitpos = nowPos + (nextPos - nowPos) * (planeNrm.Dot(nowGap)/moveDistance); localHitPos = nowPos.Plus(nextPos.Minus(nowPos).Scale(Math.abs(planeNrm.Dot(nowGap)/moveDistance))); }else{ //並行に入ってきているOR静止している if(Math.abs(planeNrm.Dot(nowGap)) > sphereRad){ //中に居ないなら、最初の地点で判定 localHitPos = nowPos; }else{ //中に居るなら、外へ押し出す //nextPos - planeNrm * (sphereRad - Abs(planeNrm.Dot(pos - nextPos))) //nextPos + planeNrm * (sphereRad - Abs(planeNrm.Dot(pos - nextPos))) localHitPos = nextPos.Plus(planeNrm.Scale(sphereRad - Math.abs(planeNrm.Dot(nextGap)))); // localHitPos = nextPos.Minus(planeNrm.Scale(sphereRad - Math.abs(planeNrm.Dot(nextGap)))); } } //交差点と中心からの距離 var hitDistanceA:Number = Math.abs(plane.m_AxisA.Dot(localHitPos.Minus(pos))); var hitDistanceB:Number = Math.abs(plane.m_AxisB.Dot(localHitPos.Minus(pos))); if(hitDistanceA <= 0.5*plane.m_WidthA && hitDistanceB <= 0.5*plane.m_WidthB){ //その距離が幅以内ならヒットしている var ratio:Number; if(moveDistance > 0){ ratio = nowPos.Minus(localHitPos).Length()/moveDistance; }else{ ratio = 0; } var localHitVel:CVector = nowVel.Lerp(nextVel, ratio);//衝突時点の速度(近似。厳密には逆算が必要) //vel -= nrm * (1 + bound)*vel.Dot(nrm) localHitVel = localHitVel.Minus(planeNrm.Scale((1.0 + sphereBoundRatio * planeBoundRatio) * localHitVel.Dot(planeNrm)));//→反射時の速度に変更 var localHitTime:Number = restTime * ratio;//衝突するまでにかかった時間(これも近似) if(localHitTime < hitTime){ //他のより先に当たっていたら採用 if(! hitPos.Equals(localHitPos)){ //前回の衝突位置とは違ったら続ける hitPlane = plane; hitTime = localHitTime; hitPos = localHitPos; hitVel = localHitVel; // Main.m_Label.text = Main.m_Label.text + "\n" +dir + ":"+ ratio; } } } } } }; hitCheckPlane(1);//片方のチェック hitCheckPlane(-1);//もう片方のチェック } //=四隅の球との衝突チェック= { var hitCheckSphere:Function = function(dirA:Number, dirB:Number):void{ //球の中心位置(中心から、片方の軸の端に移動、さらにもう片方の軸に移動、という計算) var pos:CVector = plane.m_CollisionPos.Plus( plane.m_AxisA.Normal().Scale(dirA*0.5*plane.m_WidthA)).Plus( plane.m_AxisB.Normal().Scale(dirB*0.5*plane.m_WidthB)); //球の中心と移動開始点の相対位置 var gap:CVector = nowPos.Minus(pos); //垂線の長さ var distance:Number; if(moveDistance > 0.0){ if(gap.Length() > 0.0){ //球の中心から移動線に向かって下ろした垂線 var perpendicular:CVector = movePos.Cross(movePos.Cross(gap)).Normal(); distance = Math.abs(perpendicular.Dot(gap)); }else{ distance = 0.0; } }else{ //動いてないなら、中心間の距離にしておく distance = gap.Length(); } if(distance <= sphereRad){ //そのまま移動し続ければ球の中を通る if(movePos.Dot(pos.Minus(nowPos)) >= 0.0){ //球の内側に移動する場合のみ判定 //垂線がdistance、斜線がsphereRadとした時の三角形の残りの辺の長さ var diff:Number = Math.sqrt(sphereRad*sphereRad - distance*distance); //ヒットするのに必要な距離 var hitMove:Number; if(moveDistance > 0.0){ hitMove = Math.abs(movePos.Normal().Dot(gap)) - diff; }else{ hitMove = 0.0;//gap.Length(); } if(hitMove <= moveDistance){ //球の中まで届いた=ヒットした var ratio:Number;//想定していた移動量のうち、どれだけ移動したか var localHitPos:CVector;//ヒットした位置 if(moveDistance > 0){ //移動してたら、それを元に計算 ratio = hitMove/moveDistance; localHitPos = nowPos.Plus(nextPos.Minus(nowPos).Scale(ratio)); }else{ //静止していたら、(めり込んでると思われるので)押し出し ratio = 0; localHitPos = pos.Plus(gap.Scale(sphereRad/gap.Length())); } var localHitVel:CVector = nowVel.Lerp(nextVel, ratio);//衝突時点の速度(近似) var nrm:CVector = localHitPos.Minus(pos).Normal(); //vel -= nrm * (1 + bound)*vel.Dot(nrm) localHitVel = localHitVel.Minus(nrm.Scale((1.0 + sphereBoundRatio * planeBoundRatio) * localHitVel.Dot(nrm)));//→反射時の速度 var localHitTime:Number = restTime * ratio;//ヒットまでにかかった時間(近似) if(localHitTime < hitTime){ //他のより先に当たっていたら採用 if(! hitPos.Equals(localHitPos)){ //前回の衝突位置とは違ったら続ける hitPlane = plane; hitTime = localHitTime; hitPos = localHitPos; hitVel = localHitVel; } } } } } }; hitCheckSphere( 1, 1); hitCheckSphere( 1,-1); hitCheckSphere(-1, 1); hitCheckSphere(-1,-1); } //=四つの円柱との衝突= { var hitCheckCylinder:Function = function(axisMain:CVector, axisSub:CVector, widthMain:Number, widthSub:Number):void{ //円柱の中心位置(中心から、片方の軸の端に移動という計算) var pos:CVector = plane.m_CollisionPos.Plus( axisSub.Normal().Scale(0.5*widthSub)); //円柱の中心と移動開始点の相対位置 var gap:CVector = nowPos.Minus(pos); //垂線の長さ var distance:Number; var hoge:CVector; if(moveDistance > 0.0){ //円柱の中心から移動線に向かって下ろした垂線 var perpendicular:CVector = axisMain.Cross(movePos); if(perpendicular.Length() > 0.0){ distance = Math.abs(perpendicular.Dot(gap)); hoge = perpendicular.Cross(axisMain).Normal(); }else{ //移動方向が軸と同じ //下と同じ計算 hoge = gap.Cross(axisMain).Cross(axisMain).Normal();//nowPosから円柱への垂線を入れておく distance = Math.abs(hoge.Dot(gap)); } // Main.m_Label.text = Main.m_Label.text + "\n A:" + distance; }else{ //動いてないなら、円柱から点への距離にしておく hoge = gap.Cross(axisMain).Cross(axisMain).Normal();//nowPosから円柱への垂線を入れておく distance = Math.abs(hoge.Dot(gap)); } if(distance <= sphereRad){ //そのまま移動し続ければ円柱の中を通る if(movePos.Dot(pos.Minus(nowPos)) >= 0.0){ //円柱の内側に移動する場合のみ判定 //円柱の軸方向の移動は相殺して考える var relMoveDistance:Number = Math.abs(hoge.Dot(nextPos.Minus(nowPos))); //垂線がdistance、斜線がsphereRadとした時の三角形の残りの辺の長さ var relDiff:Number = Math.sqrt(sphereRad*sphereRad - distance*distance); //ヒットするのに必要な距離 var relHitMove:Number; if(relMoveDistance > 0.0){ relHitMove = Math.abs(hoge.Dot(gap)) - relDiff; }else{ relHitMove = 0.0;//gap.Length(); } if(relHitMove <= relMoveDistance){ //円柱の中まで届いた var ratio:Number;//想定していた移動量のうち、どれだけ移動したか var localHitPos:CVector;//ヒットした位置 if(relMoveDistance > 0){ //移動してたら、それを元に計算 ratio = relHitMove/relMoveDistance; localHitPos = nowPos.Plus(nextPos.Minus(nowPos).Scale(ratio)); }else{ //静止していたら、(めり込んでると思われるので)押し出し ratio = 0; localHitPos = nowPos.Plus(hoge.Scale(sphereRad - distance));//円柱的な押し出し } var localHitVel:CVector = nowVel.Lerp(nextVel, ratio);//衝突時点の速度(近似) var nrm:CVector = localHitPos.Minus(pos).Cross(axisMain).Cross(axisMain).Normal(); //vel -= nrm * (1 + bound)*vel.Dot(nrm) localHitVel = localHitVel.Minus(nrm.Scale((1.0 + sphereBoundRatio * planeBoundRatio) * localHitVel.Dot(nrm)));//→反射時の速度 var localHitTime:Number = restTime * ratio;//ヒットまでにかかった時間(近似) var hitHeight:Number = Math.abs(localHitPos.Minus(pos).Dot(axisMain)); if(hitHeight < widthMain){ //ヒット箇所が円柱の範囲に収まっている=ヒット if(localHitTime < hitTime){ //他のより先に当たっていたら採用 if(! hitPos.Equals(localHitPos)){ //前回の衝突位置とは違ったら続ける hitPlane = plane; hitTime = localHitTime; hitPos = localHitPos; hitVel = localHitVel; // Main.m_Label.text = Main.m_Label.text + "\n" +hitVel.y + ":"+ hitPos.y; } } } } } } }; hitCheckCylinder(plane.m_AxisA, plane.m_AxisB, plane.m_WidthA, plane.m_WidthB); hitCheckCylinder(plane.m_AxisA, plane.m_AxisB.Scale(-1), plane.m_WidthA, plane.m_WidthB); hitCheckCylinder(plane.m_AxisB, plane.m_AxisA, plane.m_WidthB, plane.m_WidthA); hitCheckCylinder(plane.m_AxisB, plane.m_AxisA.Scale(-1), plane.m_WidthB, plane.m_WidthA); } } ); //何かとぶつかっていたら、新しい位置と方向を元に再び処理を開始 if(hitPlane != null){ restTime -= hitTime; nowPos = hitPos; nowVel = hitVel; nowAcc = new CVector();//微小時間の加速度は無視してみる(地面スレスレを行く場合に止まってしまうので) continue; } //ぶつかっていなければ、求めた移動先に移動して終了 sphere.m_CollisionPos = nextPos; sphere.m_CollisionVel = nextVel; break; }//end for count }//end scope //物理演算の結果→PV3D m_Sphere.x = m_PlayerSphere.m_CollisionPos.x; m_Sphere.y = m_PlayerSphere.m_CollisionPos.y; m_Sphere.z = m_PlayerSphere.m_CollisionPos.z; } //描画 public function Render():void{ //描画 m_Scene.renderCamera(m_Camera); } }