collision test
/**
* Copyright arumajirou ( http://wonderfl.net/user/arumajirou )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/ww6G
*/
package {
import flash.accessibility.Accessibility;
import flash.display.Sprite;
import flash.geom.*;
import flash.events.*;
import flash.text.*;
public class FlashTest extends Sprite {
public static var tex : TextField;
private var ref : cReferencePosition;
private var task : cCharacterTask;
public static var running : Boolean = true;
public static var marker : Sprite;
public function FlashTest() {
// write as3 code here..
tex = new TextField();
var tf : TextFormat = new TextFormat();
tf.color = 0x01;
tex.defaultTextFormat = tf;
tex.autoSize = TextFieldAutoSize.LEFT;
addChild( tex );
tex.text = "collision\n";
ref = new cReferencePosition( new Rectangle( 0, 0, stage.stageWidth, stage.stageHeight ) );
task = new cCharacterTask();
var ba : ball;
for( var l : int = 0 ; l < 5 ; l++ )
{
ba = new ball( parent, new Rectangle( 0, 0, stage.stageWidth, stage.stageHeight ) );
task.addTask( ba , 0 );
ba.addEventListener( "EVENT_COLLISION", this.addCollision );
}
parent.addEventListener( Event.ENTER_FRAME, polling );
// parent.addEventListener( MouseEvent.MOUSE_UP, mouseUp );
// parent.addEventListener( MouseEvent.MOUSE_MOVE, mouseMove );
task.addLineCollision( new cLineCollision( new Vector3D( 0, stage.stageHeight, 0, 1 ), new Vector3D( stage.stageWidth, stage.stageHeight, 0, 1 ) ) );
task.addLineCollision( new cLineCollision( new Vector3D( stage.stageWidth, 0, 0, 1 ), new Vector3D( stage.stageWidth, stage.stageHeight, 0, 1 ) ) );
task.addLineCollision( new cLineCollision( new Vector3D( 0, stage.stageHeight, 0, 1 ), new Vector3D( 0, 0, 0, 1 ) ) );
task.addLineCollision( new cLineCollision( new Vector3D( stage.stageWidth, 0, 0, 1 ), new Vector3D( 0, 0, 0, 1 ) ) );
marker = new Sprite();
parent.addChild( marker );
}
public function addCollision( e : Event ) : void
{
task.addCollision( cCollisionEvent(e).colli );
}
public function mouseUp( e : MouseEvent ) : void
{
// if( running == true ) running = false;
if( running == false ) running = true;
}
public function mouseMove( e : MouseEvent ) : void
{
if( running == false )
{
run();
}
}
public function run() : void
{
marker.graphics.clear();
tex.text = "";
task.poll( ref );
}
public function polling( e : Event ) : void
{
if( running == true )
{
run();
}
}
}
}
import flash.events.*;
import flash.geom.*;
import flash.display.*;
class cSubject{ private var observers : Vector.<iObserver>; public function cSubject() { observers = new Vector.<iObserver>(); } public function attach( o : iObserver ) : void { observers.push( o ); } public function detach( o : iObserver ) : void { var result : int = observers.indexOf( o ); if( result == -1 ) return; observers.splice( result, 1 ); } public function notify( o : Object ) : void { var len : int = observers.length; for( var i : int = 0 ; i < len ; i++ ) { observers[ i ].update( o ); } }}interface iObserver{ function update( o : Object ) : void;}
class cCollisionEvent extends Event
{
public var colli : iCollision;
public function cCollisionEvent( t : iCollision = null )
{
super( "EVENT_COLLISION" );
colli = t;
}
}
class cReferencePosition
{
private var displayArea : Rectangle;
private var position : Vector3D;
public function cReferencePosition( a : Rectangle = null )
{
displayArea = a;
update( new Vector3D( 0,0,0,1 ) );
}
public function setter( v : Vector3D ) : void
{
position = v.clone();
}
public function getter() : Vector3D
{
return position.clone();
}
public function update( vec : Vector3D ) : Vector3D
{
// return ( position = ( new Vector3D( displayArea.width / 2, displayArea.height / 2, 0, 1 ) ).subtract( vec ) );
return ( position = ( new Vector3D( 0, 0, 0, 1 ) ).subtract( vec ) );
}
}
class cCharacterTask
{
static public const MAX_DEPTH : int = 3;
private var list : Vector.<Vector.<iCharacter>>;
private var removes : Vector.<iCharacter>;
private var collisions : Vector.<iCollision>;
private var line : Vector.<iCollision>;
public function cCharacterTask()
{
list = new Vector.<Vector.<iCharacter>>( cCharacterTask.MAX_DEPTH );
removes = new Vector.<iCharacter>();
collisions = new Vector.<iCollision>();
line = new Vector.<iCollision>();
for( var i : int = 0 ; i < cCharacterTask.MAX_DEPTH ; i++ )
{
list[i] = ( new Vector.<iCharacter>() );
}
}
public function addTask( t : iCharacter, depth : int ) : void
{
list[ depth ].push( t );
}
public function removeTask( t : iCharacter ) : void
{
removes.push( t );
}
public function addCollision( t : iCollision ) : void
{
collisions.push( t );
}
public function addLineCollision( t : iCollision ) : void
{
line.push( t );
}
public function poll( pos : cReferencePosition ) : void
{
for( var i : int = 0 ; i < cCharacterTask.MAX_DEPTH ; i++ )
{
var len : int = list[ i ].length;
for( var j : int = 0 ; j < len ; j++ )
{
list[ i ][ j ].process();
}
}
for each( var ct : iCollision in line )
{
addCollision( ct );
}
len = collisions.length;
var loops : int = 0;
for( i = 0 ; i < len ; i++ )
{
for( j = i + 1 ; j < len ; j++ )
{
if( collisions[ i ].hitTest( collisions[ j ] ) == true )
{
i = 0;
j = 0;
loops++;
continue;
}
}
if( loops > 100 ) break; // 保険
}
var v : Vector3D = pos.getter();
for( i = 0 ; i < cCharacterTask.MAX_DEPTH ; i++ )
{
len = list[ i ].length;
for( j = 0 ; j < len ; j++ )
{
list[ i ][ j ].draw( v );
}
}
for each( var t : iCharacter in removes )
{
for( i = 0 ; i < cCharacterTask.MAX_DEPTH ; i++ )
{
var num : int = 0;
if( ( num = list[ i ].indexOf( t ) ) != -1 )
{
list[ i ].splice( num, 1 );
}
}
}
removes.splice( 0, removes.length );
collisions.splice( 0, collisions.length );
}
}
interface iCharacter
{
function process() : void;
function draw( v : Vector3D ) : void;
}
class cCharacter extends EventDispatcher implements iCharacter
{
protected var _stage : DisplayObjectContainer = null;
protected var _visual : Sprite = null;
protected var _pos : Vector3D = null;
protected var _oldPos : Vector3D = null;
public function cCharacter( d : DisplayObjectContainer = null )
{
_stage = d;
_pos = new Vector3D( 0, 0, 0, 1 );
}
public function process() : void
{
}
public function draw( v : Vector3D ) : void
{
}
}
class cMovableCharacter extends cCharacter
{
protected var region : Rectangle = null;
protected var _moves : Vector3D = null;
protected var hostility : int = -1;
protected var life : int = 0;
public function cMovableCharacter( d : DisplayObjectContainer = null, r : Rectangle = null )
{
super(d);
region = r;
_moves = new Vector3D( 15, 0, 0, 0 );
}
public override function process() : void
{
_oldPos = _pos.clone();
_pos = _pos.add( _moves );
_pos.x = ( _pos.x - region.left + region.width ) % region.width + region.left;
_pos.y = ( _pos.y - region.top + region.height ) % region.height + region.top;
}
public override function draw( v : Vector3D ) : void
{
_visual.x = ( _pos.x + v.x + region.width ) % region.width;
_visual.y = ( _pos.y + v.y + region.height ) % region.height;
}
}
class ball extends cMovableCharacter implements iObserver
{
private var colli : cMovingCircleCollision;
private var onDrag : Boolean;
private var dragPos : Vector3D;
private var radius : Number;
public function ball( d : DisplayObjectContainer = null, r : Rectangle = null )
{
super( d, r );
_visual = new Sprite();
d.addChild( _visual );
radius = 15 + Math.random() * 30;
const g : Graphics = _visual.graphics;
g.clear();
g.lineStyle( 1, 0x00ff00 );
g.beginFill( Math.random() * 0xffffff );
g.drawCircle( 0, 0, radius );
g.endFill();
_visual.addEventListener( MouseEvent.MOUSE_MOVE, this.move );
_visual.addEventListener( MouseEvent.MOUSE_DOWN, this.mouseDown);
_visual.addEventListener( MouseEvent.MOUSE_UP, this.mouseUp );
_visual.addEventListener( Event.ENTER_FRAME, this.enter );
_pos.x = Math.random() * ( r.width - radius * 2 ) + radius;
_pos.y = Math.random() * ( r.width - radius * 2 ) + radius;
onDrag = false;
}
public override function process() : void
{
var p0 : Vector3D = _pos.clone();
super.process();
colli = new cMovingCircleCollision( p0, _pos, radius );
colli.attach( this );
var vg : Vector3D = new Vector3D( 0, 0.21, 0, 0 );
if( onDrag == false )
_moves = _moves.add( vg );
dispatchEvent( new cCollisionEvent( colli ) );
}
public function mouseDown( e : MouseEvent ) : void
{
if( onDrag == true ) return;
onDrag = true;
_visual.startDrag( true );
_moves = new Vector3D( 0, 0, 0, 0 );
}
public function mouseUp( e : MouseEvent ) : void
{
onDrag = false;
_visual.stopDrag();
_moves = _pos.subtract( dragPos );
}
public function move( e : MouseEvent ) : void
{
if( onDrag == false ) return;
dragPos = _pos.clone();
_pos.x = e.stageX;
_pos.y = e.stageY;
}
public function enter( e : Event ) : void
{
if( onDrag == false ) return;
dragPos = _pos.clone();
}
public function update( o : Object ) : void
{
if( o.collision == true )
{
_pos = o.pos;
_moves = o.reflection;
_moves.scaleBy( 0.5 );
colli.process( o.contact, _pos );
// FlashTest.tex.appendText( "移動量:" + _moves.toString() + "\n" );
}
}
}
class wall extends cCharacter
{
}
class cVectorUtil
{
static public function procReflection( l : Vector3D, n : Vector3D ) : Vector3D
{
var proj : Vector3D = l.clone();
proj.scaleBy( -1 );
var nc : Vector3D = n.clone();
nc.scaleBy( proj.dotProduct( n ) );
nc = proj.subtract( nc );
nc.scaleBy( -2 );
return proj.add( nc );
}
}
interface iCollision
{
function hitTest( c : iCollision ) : Boolean;
}
class cLineCollision extends cSubject implements iCollision
{
private var p0 : Vector3D;
private var p1 : Vector3D;
private var normal : Vector3D;
private var d : Number;
public function cLineCollision( a : Vector3D = null, b : Vector3D = null )
{
p0 = a;
p1 = b;
var n : Vector3D = a.subtract( b );
n.normalize();
normal = new Vector3D( n.y, n.x, 0, 0 );
d = -( normal.dotProduct( p0 ) );
// FlashTest.tex.appendText( "" + normal.toString() + "\n" );
}
public function hitTest( c : iCollision ) : Boolean
{
return false;
}
public function getNormal() : Vector3D
{
return normal.clone();
}
public function getD() : Number
{
return d;
}
}
class cMovingCircleCollision extends cSubject implements iCollision
{
private var p : Vector3D;
private var r : Number;
private var v : Vector3D;
public function cMovingCircleCollision( p1 : Vector3D = null, p2 : Vector3D = null, rad : Number = 0 )
{
r = rad;
process( p1, p2 );
const g : Graphics = FlashTest.marker.graphics;
g.lineStyle( 1, 0xff0000 );
g.moveTo( p.x, p.y );
g.lineTo( p.x + v.x, p.y + v.y );
}
public function process( p1 : Vector3D, p2 : Vector3D ) : void
{
p = p1;
v = p2.subtract( p1 );
}
public function getPos() : Vector3D
{
return p;
}
public function getMoves() : Vector3D
{
return v;
}
public function getRadius() : Number
{
return r;
}
public function hitTestWithLine( c : iCollision ) : Boolean
{
const l : cLineCollision = cLineCollision(c);
const n : Vector3D = l.getNormal();
const m : Number = v.dotProduct( n );
if( m >= 0 ) return false;
const t : Number = -( (n.dotProduct(p)+l.getD()-r) / m );
if( ( t < 0 ) || ( t > 1 ) ) return false;
// 接触時の中心座標を算出
var c0 : Vector3D = v.clone();
c0.scaleBy( t );
c0 = c0.add( p );
// 未処理の運動量を算出
var en : Vector3D = v.clone();
en.scaleBy( 1 - t );
en = cVectorUtil.procReflection( en, n );
notify(
{
"collision" : true,
"time" : t,
"contact" : c0,
"pos" : c0.add( en ),
"normal" : n,
"reflection" : cVectorUtil.procReflection( v, n )
} );
return true
}
public function hitTestWithMovingCircle( c : iCollision ) : Boolean
{
const tc : cMovingCircleCollision = cMovingCircleCollision( c );
const Pq : Vector3D = tc.getPos();
const Vq : Vector3D = tc.getMoves();
var A : Vector3D = p.subtract( Pq );
var B : Vector3D = v.subtract( Vq );
var AB : Number = A.dotProduct( B );
var BB : Number = B.dotProduct( B );
if( BB == 0 ) return false;
var t : Number = - ( AB / BB );
if( t < 0 ) t = 0;
if( t > 1 ) t = 1;
var d : Number = A.dotProduct( A ) + ( 2 * t * AB ) + t * t *BB;
var range : Number = r + tc.getRadius();
if( d > (range*range) ) return false;
// 判定終了
//FlashTest.running = false;
// 使わないけど正確な接触時間tを再計算
t = ( AB * AB ) - ( BB * ( A.dotProduct( A ) - ( range * range ) ) );
t = Math.sqrt( t );
t = -AB - t;
t = t / BB;
if( ( t < 0 ) || ( t > 1 ) ) return false;
var tv : Vector3D = v.clone();
tv.scaleBy( t );
var pt : Vector3D = p.add( tv );
var tVq : Vector3D = Vq.clone();
tVq.scaleBy( t );
var qt : Vector3D = Pq.add( tVq );
var n : Vector3D = qt.subtract( pt );
n.normalize();
var nn : Vector3D = n.clone();
nn.scaleBy( -1 );
var c0 : Vector3D = n.clone();
c0.scaleBy( r );
c0 = pt.add( c0 );
// エネルギーを適当に相殺
var en : Number = tv.dotProduct( tv ) + tVq.dotProduct( tVq );
en = Math.sqrt( en ) / 2;
tv.normalize();tv.scaleBy( en );
tVq.normalize();tVq.scaleBy( en );
// 未処理のエネルギーを算出(てきとー)
var eTv : Vector3D = v.clone();
eTv.scaleBy( 1 - t );
var enn : Number = en - Math.sqrt( eTv.dotProduct( eTv ) );
if( enn < 0 ) enn = 0;
eTv.normalize();
eTv.scaleBy( enn );
eTv = cVectorUtil.procReflection( eTv, nn );
var eTvq : Vector3D = eTv.clone();
eTvq.scaleBy( -1 );
var ref : Vector3D = eTv.clone();
ref = ref.add( c0 );
const g : Graphics = FlashTest.marker.graphics;
g.lineStyle( 1, 0x0000ff );
g.moveTo( c0.x, c0.y );
g.lineTo( ref.x, ref.y );
notify(
{
"collision" : true,
"time" : t,
"contact" : pt,
"pos" : pt.add( eTv ),
"normal" : nn,
"reflection" : cVectorUtil.procReflection( v, nn )
} );
tc.notify(
{
"collision" : true,
"time" : t,
"contact" : qt,
"pos" : qt.add( eTvq ),
"normal" : n,
"reflection" : cVectorUtil.procReflection( tVq, n )
} );
return true;
}
public function hitTest( c : iCollision ) : Boolean
{
if( c is cMovingCircleCollision ) return hitTestWithMovingCircle( c );
if( c is cLineCollision ) return hitTestWithLine( c );
return false;
}
}