forked from: forked from: 2次ベジェ曲線とボールの衝突判定
マウスオーバーでハンドルを表示、ハンドルをドラッグで曲線を変形できます。
/**
* Copyright tepe ( http://wonderfl.net/user/tepe )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/9U9x
*/
// forked from tepe's forked from: 2次ベジェ曲線とボールの衝突判定
// forked from poiasd's 2次ベジェ曲線とボールの衝突判定
/**
* マウスオーバーでハンドルを表示、ハンドルをドラッグで曲線を変形できます。
*/
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
[SWF (width = "465", height = "465", frameRate = "60", backgroundColor = "0xFFFFFF")]
public class Main extends Sprite {
private const _SIZE:Number = 20;
private const _RADIUS:Number = _SIZE * 0.5;
private const _GRAVITY:Number = 0.1;
private const _COR:Number = 0.6;
private const _LIMIT_SPEED:Number = 10;
private var _stageWidth:int;
private var _stageHeight:int;
private var _ball:Ball;
private var b2:Ball;
private var _curveList:Array = [];
private var _target:Handle = null;
private var bList:Array = new Array();
public function Main() {
_init();
}
public function addBall(x:Number=0,y:Number=0,s:Number=_SIZE):void{
bList.push(new Ball(x,y,s));
addChild(bList[bList.length-1]);
}
private function _init():void {
stage.scaleMode = "noScale";
_stageWidth = stage.stageWidth;
_stageHeight = stage.stageHeight;
graphics.beginFill(0x111111);
graphics.drawRect(0, 0, _stageWidth, _stageHeight);
graphics.endFill();
addBall(50,10,_SIZE);
addBall(100,0);
addBall();
var start:Point = new Point();
var control:Point = new Point();
var anchor:Point = new Point();
//曲線パラメータ
var list:Array = [[ 40, 70, 40, 191, 282, 124],
[197, 219, 409, 213, 437, 69],
[ 25, 240, 140, 240, 180, 340],
[180, 340, 220, 440, 310, 320],
[380, 350, 410, 420, 440, 350]];
for (var i:int = 0; i < list.length; i++) {
start.x = list[i][0], start.y = list[i][1];
control.x = list[i][2], control.y = list[i][3];
anchor.x = list[i][4], anchor.y = list[i][5];
_createCurve(start, control, anchor);
}
stage.addEventListener(MouseEvent.MOUSE_OVER, _mouseOverHandler);
stage.addEventListener(MouseEvent.MOUSE_OUT, _mouseOutHandler);
stage.addEventListener(MouseEvent.MOUSE_DOWN, _mouseDownHandler);
stage.addEventListener(MouseEvent.MOUSE_UP, _mouseUpHandler);
addEventListener(Event.ENTER_FRAME, _enterFrameHandler);
}
private function _mouseOverHandler(event:MouseEvent):void {
var name:String = event.target.name;
var curve:Curve;
if (name == "curve") {
curve = Curve(event.target);
} else if (name == "handle") {
curve = Curve(event.target.parent);
} else {
return;
}
curve.show();
setChildIndex(curve, numChildren - 1);
}
private function _mouseOutHandler(event:MouseEvent):void {
var name:String = event.target.name;
var curve:Curve;
if (name == "curve") {
curve = Curve(event.target);
} else if (name == "handle") {
curve = Curve(event.target.parent);
} else {
return;
}
curve.hide();
}
private function _mouseDownHandler(event:MouseEvent):void {
if (event.target.name == "handle") {
_target = event.target as Handle;
_target.startDrag();
stage.addEventListener(MouseEvent.MOUSE_MOVE, _mouseMoveHandler);
stage.removeEventListener(MouseEvent.MOUSE_OVER, _mouseOverHandler);
stage.removeEventListener(MouseEvent.MOUSE_OUT, _mouseOutHandler);
}
}
private function _mouseMoveHandler(event:MouseEvent):void {
Curve(_target.parent).draw();
event.updateAfterEvent();
}
private function _mouseUpHandler(event:MouseEvent):void {
if (_target) {
Curve(_target.parent).draw();
_target.stopDrag();
_target = null;
stage.removeEventListener(MouseEvent.MOUSE_MOVE, _mouseMoveHandler);
stage.addEventListener(MouseEvent.MOUSE_OVER, _mouseOverHandler);
stage.addEventListener(MouseEvent.MOUSE_OUT, _mouseOutHandler);
}
}
//------ メインフレーム ------------------------------
private function _enterFrameHandler(e:Event):void {
//ballUpdate(_ball);
//ballUpdate(b2);
for(var i:int=0;i<bList.length;i++){
ballUpdate(bList[i]);
}
}
private function ballUpdate(b:Ball):void{
var vel:Point = b.velocity;
vel.x = Math.min(_LIMIT_SPEED, Math.max(vel.x, -_LIMIT_SPEED));
vel.y = Math.min(_LIMIT_SPEED, Math.max(vel.y += _GRAVITY, -_LIMIT_SPEED));
//ボール位置更新 画面ループ
b.x += vel.x;
(b.x %= _stageWidth) < 0 ? b.x += _stageWidth : 0;//画面ループ
b.y += vel.y;
(b.y %= _stageHeight) < 0 ? b.y += _stageHeight : 0;//画面ループ
var ballRect:Rectangle = b.getRect(this);
var d:Number = vel.length - b.size;//_SIZE;
if (d > 0) ballRect.inflate(d, d);
for each(var curve:Curve in _curveList) {
if (ballRect.intersects(curve.rect)) {
_collisionBezier(b, curve);
}
}
}
//曲線との当たり判定と処理
private function _collisionBezier(ball:Ball, curve:Curve):void {
var start:Point = curve.start;
var control:Point = curve.control;
var anchor:Point = curve.anchor;
var pos:Point = ball.position;
var vel:Point = ball.velocity;
var t:Number = BezierUtil.getTForClosestPoint(start, control, anchor, pos);
var value:Point = BezierUtil.getValue(start, control, anchor, t);
// すり抜け防止処理
// _RADIUS > _LIMIT_SPEED * Math.sqrt(2) の場合は必要ないです
// 他の曲線との干渉によるすり抜けは防げないのであまり意味ないかも
var v1:Point = new Point(value.x - pos.x, value.y - pos.y);
var v2:Point = new Point(v1.x + vel.x, v1.y + vel.y);
var dot:Number = v1.x * v2.x + v1.y * v2.y;
if (dot <= 0) {
pos.offset(-vel.x, -vel.y);
var v:Point = vel.clone();
v.normalize(_RADIUS * 0.5);
var d:Number = vel.length;
var count:int = Math.ceil(d / (_RADIUS * 0.5));
for (var i:int = 0; i < count; i++) {
pos.x += v.x;
pos.y += v.y;
t = BezierUtil.getTForClosestPoint(start, control, anchor, pos);
value = BezierUtil.getValue(start, control, anchor, t);
if (d - (d = Point.distance(pos, value)) < 0) return;
if (d <= _RADIUS) break;
}
}
var dx:Number = pos.x - value.x;
var dy:Number = pos.y - value.y;
var dist:Number = Math.sqrt(dx * dx + dy * dy);
if (dist > _RADIUS) return;
var cos:Number = dx / dist;
var sin:Number = dy / dist;
pos = _coordinateRotation(0, _RADIUS, sin, -cos);
ball.x = value.x + pos.x;
ball.y = value.y + pos.y;
vel = _coordinateRotation(vel.x, vel.y, sin, cos);
ball.velocity = _coordinateRotation(vel.x, -vel.y * _COR, sin, -cos);
}
private function _coordinateRotation(x:Number, y:Number, cos:Number, sin:Number):Point {
return new Point(x * cos - y * sin, x * sin + y * cos);
}
private function _createCurve(start:Point, control:Point, anchor:Point):void {
var curve:Curve = new Curve(start, control, anchor);
addChild(curve);
_curveList.push(curve);
}
}
}
import flash.display.*;
import flash.geom.*;
import flash.events.*;
class Ball extends Sprite {
public var velocity:Point = new Point();
private var _size:Number;
public function get size():Number{
return _size;
}
public function Ball(x:Number, y:Number, s:Number) {
_size = s;
graphics.beginFill(0xFFFFFF, 0.8);
graphics.drawCircle(0, 0, _size * 0.5);
graphics.endFill();
this.x = x;
this.y = y;
//ドラッグ
this.addEventListener(MouseEvent.MOUSE_DOWN,function(e:MouseEvent):void{
addEventListener(Event.ENTER_FRAME,dragBall);
});
//リリース
this.addEventListener(MouseEvent.MOUSE_UP,function(e:MouseEvent):void{
removeEventListener(Event.ENTER_FRAME,dragBall);
});
//ロールオーバー
this.addEventListener(MouseEvent.MOUSE_OVER,function():void{
with(graphics){
clear();
lineStyle(2,0x2498F6, 0.6);
drawCircle(0, 0, _size * 0.6);
lineStyle(0,0,0);
beginFill(0xFFFFFF, 0.8);
drawCircle(0, 0, _size * 0.5);
endFill();
}
});
//ロールアウト
this.addEventListener(MouseEvent.MOUSE_OUT,function():void{
with(graphics){
clear();
beginFill(0xFFFFFF, 0.8);
drawCircle(0, 0, _size * 0.5);
endFill();
}
});
}
public function get position():Point {
return new Point(x, y);
}
private var sx:Number;
private var sy:Number;
private function dragBall(e:Event):void{
sx = -(this.x - this.parent.mouseX)/2;
sy = -(this.y - this.parent.mouseY)/2;
sx *= 0.8;
sy *= 0.8;
this.x = this.parent.mouseX;
this.y = this.parent.mouseY;
//velocity.x=0; velocity.y=0;
velocity.x=sx; velocity.y=sy;
}
}
class Curve extends Sprite {
private var _start:Handle;
private var _control:Handle;
private var _anchor:Handle;
private var _shape:Shape;
private var _hitArea:Sprite;
public function Curve(start:Point, control:Point, anchor:Point) {
_start = new Handle(start.x, start.y);
addChild(_start);
_control = new Handle(control.x, control.y);
addChild(_control);
_anchor = new Handle(anchor.x, anchor.y);
addChild(_anchor);
_shape = new Shape();
addChild(_shape);
_hitArea = new Sprite();
_hitArea.mouseEnabled = false;
_hitArea.visible = false;
addChild(_hitArea);
hitArea = _hitArea;
name = "curve";
draw();
hide();
}
public function show():void {
graphics.clear();
graphics.lineStyle(0, 0x2498F6, 0.6);
graphics.moveTo(_start.x, _start.y);
graphics.lineTo(_control.x, _control.y);
graphics.lineTo(_anchor.x, _anchor.y);
_start.alpha = 1;
_control.alpha = 1;
_anchor.alpha = 1;
}
public function hide():void {
graphics.clear();
_start.alpha = 0;
_control.alpha = 0;
_anchor.alpha = 0;
}
public function draw():void {
_shape.graphics.clear();
_shape.graphics.lineStyle(1, 0xFFFFFF, 0.8);
_shape.graphics.moveTo(_start.x, _start.y);
_shape.graphics.curveTo(_control.x, _control.y, _anchor.x, _anchor.y);
_hitArea.graphics.clear();
_hitArea.graphics.lineStyle(20, 0, 1);
_hitArea.graphics.moveTo(_start.x, _start.y);
_hitArea.graphics.lineTo(_control.x, _control.y);
_hitArea.graphics.lineTo(_anchor.x, _anchor.y);
show();
}
public function get start():Point {
return new Point(_start.x, _start.y);
}
public function get control():Point {
return new Point(_control.x, _control.y);
}
public function get anchor():Point {
return new Point(_anchor.x, _anchor.y);
}
public function get rect():Rectangle {
return _shape.getBounds(this);
}
}
class Handle extends Sprite {
public function Handle(x:Number = 0, y:Number = 0) {
graphics.beginFill(0x2498F6, 0.4);
graphics.drawCircle(0, 0, 5);
graphics.beginFill(0x24C8F6, 0.8);
graphics.drawCircle(0, 0, 2);
graphics.endFill();
this.x = x;
this.y = y;
name = "handle";
buttonMode = true;
}
}
class BezierUtil {
public static function getValue(start:Point, control:Point, anchor:Point, t:Number):Point {
var m:Number = 1 - t;
var x:Number = m * m * start.x + 2 * t * m * control.x + t * t * anchor.x;
var y:Number = m * m * start.y + 2 * t * m * control.y + t * t * anchor.y;
return new Point(x, y);
}
public static function getControlPoint(start:Point, middle:Point, end:Point):Point {
var x:Number = (4 * middle.x - start.x - end.x) * 0.5;
var y:Number = (4 * middle.y - start.y - end.y) * 0.5;
return new Point (x, y);
}
public static function getLength(start:Point, control:Point, anchor:Point, t:Number):Number {
_initIntegration(start, control, anchor);
return _calculateLength(t);
}
public static function getTForX(start:Point, control:Point, anchor:Point, x:Number):Array {
var ax:Number = start.x - 2 * control.x + anchor.x;
var bx:Number = 2 * (control.x - start.x);
var cx:Number = start.x - x;
var answer:Array = _quadraticFormula(ax, bx, cx);
var i:int = answer.length;
while (i--) {
var t:Number = answer[i];
if (t < 0 || t > 1) answer.splice(i, 1);
}
return answer;
}
public static function getTForY(start:Point, control:Point, anchor:Point, y:Number):Array {
var ay:Number = start.y - 2 * control.y + anchor.y;
var by:Number = 2 * (control.y - start.y);
var cy:Number = start.y - y;
var answer:Array = _quadraticFormula(ay, by, cy);
var i:int = answer.length;
while (i--) {
var t:Number = answer[i];
if (t < 0 || t > 1) answer.splice(i, 1);
}
return answer;
}
public static function getTForLength(start:Point, control:Point, anchor:Point, length:Number):Number {
_initIntegration(start, control, anchor);
var totalLength:Number = _calculateLength(1);
var t:Number = length / totalLength;
if (t <= 0 || t >= 1) {
return t <= 0 ? 0 : 1;
}
var temp:Number = _calculateLength(t);
var d:Number = length - temp;
while (Math.abs(d) > 0.01) {
t += d / totalLength;
temp = _calculateLength(t);
d = length - temp;
}
return t;
}
public static function getTForClosestPoint(start:Point, control:Point, anchor:Point, point:Point):Number {
var ax:Number = start.x - 2 * control.x + anchor.x;
var bx:Number = control.x - start.x;
var cx:Number = start.x;
var ay:Number = start.y - 2 * control.y + anchor.y;
var by:Number = control.y - start.y;
var cy:Number = start.y;
var a:Number = - (ax * ax + ay * ay);
var b:Number = - 3 * (ax * bx + ay * by);
var c:Number = ax * (point.x - cx) - 2 * bx * bx + ay * (point.y - cy) - 2 * by * by;
var d:Number = bx * (point.x - cx) + by * (point.y - cy);
var answer:Array = _cubicFormula(a, b, c, d);
var minimum:Number = Number.MAX_VALUE;
var length:int = answer.length;
var t:Number;
for (var i:int = 0; i < length; i++) {
if (answer[i] < 0) answer[i] = 0;
else if (answer[i] > 1) answer[i] = 1;
var distance:Number = Point.distance(point, getValue(start, control, anchor, answer[i]));
if (distance < minimum) {
t = answer[i];
minimum = distance;
}
}
return t;
}
public static function getTForIntersectionOfLine(start:Point, control:Point, anchor:Point, a:Number, b:Number, c:Number):Array {
var aa:Number = a * (start.x - 2 * control.x + anchor.x) + b * (start.y - 2 * control.y + anchor.y);
var bb:Number = 2 * a * (control.x - start.x) + 2 * b * (control.y - start.y);
var cc:Number = a * start.x + b * start.y + c;
var answer:Array = _quadraticFormula(aa, bb, cc);
var i:int = answer.length;
while (i--) {
var t:Number = answer[i];
if (t < 0 || t > 1) answer.splice(i, 1);
}
return answer;
}
private static var _ax:Number;
private static var _ay:Number;
private static var _bx:Number;
private static var _by:Number;
private static var _A:Number;
private static var _B:Number;
private static var _C:Number;
private static function _initIntegration(start:Point, control:Point, anchor:Point):void {
_ax = start.x - 2 * control.x + anchor.x;
_ay = start.y - 2 * control.y + anchor.y;
_bx = control.x - start.x;
_by = control.y - start.y;
_A = _ax * _ax + _ay * _ay;
_B = _ax * _bx + _ay * _by;
_C = _bx * _bx + _by * _by;
if (_A != 0) {
_B = _B / _A;
_C = _C / _A - _B * _B;
_A = Math.sqrt(_A);
}
}
private static function _integrate(t:Number):Number {
var m:Number = _B + t;
var n:Number = Math.sqrt(m * m + _C);
if (_C <= 0) return _A * m * n;
return _A * (m * n + _C * Math.log(m + n));
}
private static function _calculateLength(t:Number):Number {
if (_A == 0) return Math.sqrt(4 * _C) * t;
return _integrate(t) - _integrate(0);
}
private static function _quadraticFormula(a:Number, b:Number, c:Number):Array {
var answer:Array = [];
if (a == 0) {
if (b != 0) answer.push(- c / b);
} else {
var D:Number = b * b - 4 * a * c;
if (D > 0) {
D = Math.sqrt(D);
answer.push((- b - D) / (2 * a), (- b + D) / (2 * a));
} else if (D == 0) {
answer.push(- b / (2 * a));
}
}
return answer;
}
private static function _cubicFormula(a:Number, b:Number, c:Number, d:Number):Array {
if (a == 0) {
return _quadraticFormula(b, c, d);
}
var answer:Array = [];
var q:Number = (3 * a * c - b * b) / (9 * a * a);
var r:Number = (9 * a * b * c - 27 * a * a * d - 2 * b * b * b) / (54 * a * a * a);
if (q == 0) {
answer.push((r < 0 ? -1 : 1) * Math.pow(Math.abs(r), 1 / 3) - b / (3 * a));
} else {
var D:Number = q * q * q + r * r;
if (D > 0) {
D = Math.sqrt(D);
var s:Number = r + D;
s = (s < 0 ? -1 : 1) * Math.pow(Math.abs(s), 1 / 3);
var t:Number = r - D;
t = (t < 0 ? -1 : 1) * Math.pow(Math.abs(t), 1 / 3);
answer.push(s + t - b / (3 * a));
} else {
var u:Number = Math.sqrt(-q);
var theta:Number = Math.acos(r / Math.sqrt(-Math.pow(q, 3)));
answer.push(2 * u * Math.cos(theta / 3) - b / (3 * a));
answer.push(2 * u * Math.cos((theta + 2 * Math.PI) / 3) - b / (3 * a));
answer.push(2 * u * Math.cos((theta + 4 * Math.PI) / 3) - b / (3 * a));
}
}
return answer;
}
}