Flash/Flex学习笔记(40):正向运动学
所谓"正向运动学"通俗点讲就是把几个连接部件的一端固定起来,另一个端可以自由(向前/向外)运动。比如人的行走,单个下肢可以理解为脚连接小腿,小腿连接大腿,大腿连接腰。行走的过程,相当于二条腿相对固定于腰部,大腿运动驱动小腿,小腿又驱动脚,从而带动整个连接系统的一系列运动。
先来一个基本的关节类Segment:(就是一个圆角矩形+二个小圆圈)
package
{
?import
flash.display.Sprite;
import
flash.geom.Point;
public
class
Segment
extends
Sprite {
?private
var
color:
uint
;
?private
var
segmentWidth:
Number
;
private
var
segmentHeight:
Number
;
?public
var
vx:
Number
=
0
;
?public
var
vy:
Number
=
0
;
public
function
Segment(segmentWidth:
Number
,segmentHeight:
Number
,color:
uint
=
0xffffff
) {
?this
.segmentWidth=segmentWidth;
this
.segmentHeight=segmentHeight;
?this
.color=color;
?init();
?}
?public
function
init():
void
{
?// 绘制关节?
?graphics.lineStyle(
0
);
graphics.beginFill(color);
?graphics.drawRoundRect(- segmentHeight/
2
,- segmentHeight/
2
,segmentWidth+segmentHeight,segmentHeight,segmentHeight,segmentHeight);
graphics.endFill();
?// 绘制两个“枢轴”?
?graphics.drawCircle(
0
,
0
,
2
);?
?graphics.drawCircle(segmentWidth,
0
,
2
);
?}
//获得自由端的坐标?
?public
function
getPin():Point {
?var
angle:
Number
=rotation*Math.PI/
180
;
?var
xPos:
Number
=x+Math.cos(angle)*segmentWidth;
?var
yPos:
Number
=y+Math.sin(angle)*segmentWidth;
return
new
Point(xPos,yPos);
?}
?}
?}
为了动态控制关节的旋转,再来一个简单的滑块控件类:(下列代码看起来吃力的同学,建议先看Flash/Flex学习笔记(36):自己动手实现一个滑块控件(JimmySilder))
package
{
?import
flash.display.Sprite;
?import
flash.events.MouseEvent;
?import
flash.geom.Rectangle;
?import
flash.events.Event;
?public
class
SimpleSlider
extends
Sprite {
?private
var
_width:
Number
=
6
;
?private
var
_height:
Number
=
100
;
private
var
_value:
Number
;
private
var
_max:
Number
=
100
;
?private
var
_min:
Number
=
0
;
private
var
_handle:Sprite;
private
var
_back:Sprite;
private
var
_backWidth:
Number
=
0
;
?private
var
_handleHeight:
Number
=
20
;
?private
var
_backColor:
uint
=
0xcccccc
;
?private
var
_backBorderColor:
uint
=
0x999999
;
private
var
_handleColor:
uint
=
0x000000
;
private
var
_handleBorderColor:
uint
=
0xcccccc
;
private
var
_handleRadius:
Number
=
2
;
private
var
_backRadius:
Number
=
2
;
public
function
SimpleSlider(min:
Number
=
0
, max:
Number
=
100
, value:
Number
=
100
) {
_min=min;
_max=max;
?value=Math.min(Math.max(value,min),max);
init();
}
?
private
function
init():
void
{
_back =
new
Sprite () ;
?addChild(_back);
?_handle =
new
Sprite () ;
?_handle.buttonMode=
true
;
addChild(_handle);
?_handle.addEventListener( MouseEvent.MOUSE_DOWN , MouseDownHandler );
draw();
updatePosition();
}
private
function
draw():
void
{
?drawBack();
?drawHandle();
?}
?private
function
drawBack():
void
{
?_back.graphics.clear();
?_back.graphics.beginFill( _backColor );
?_back.graphics.lineStyle(
0
, _backBorderColor );
?_back.graphics.drawRoundRect(
0
,
0
, _backWidth , _height , _backRadius , _backRadius );
?_back.graphics.endFill();
?_back.x=_width/
2
-_backWidth/
2
;
?}
private
function
drawHandle():
void
{
?_handle.graphics.clear();
?_handle.graphics.beginFill( _handleColor );
?_handle.graphics.lineStyle(
0
, _handleBorderColor );
?_handle.graphics.drawRect(
0
,
0
, _width , _handleHeight );
_handle.graphics.endFill();
?}
?private
function
updatePosition():
void
{
?var
handleRange:
Number
=_height-_handleHeight;
?var
valueRange:
Number
=_max-_min;
?_handle.y = handleRange - ( _value - _min ) / valueRange * handleRange ;
?}
?private
function
updateValue():
void
{
?var
handleRange:
Number
=_height-_handleHeight;
?var
valueRange:
Number
=_max-_min;
?_value = ( handleRange - _handle.y ) / handleRange * valueRange + _min ;
dispatchEvent(
new
Event ( Event.CHANGE ));
?}
private
function
MouseUpHandler( e:MouseEvent ):
void
{
?stage.removeEventListener( MouseEvent.MOUSE_MOVE , MouseMoveHandler );
?stage.removeEventListener( MouseEvent.MOUSE_UP , MouseUpHandler );
?_handle.stopDrag();
?}
?private
function
MouseDownHandler( e:MouseEvent ):
void
{
?stage.addEventListener( MouseEvent.MOUSE_MOVE , MouseMoveHandler );
?stage.addEventListener( MouseEvent.MOUSE_UP , MouseUpHandler );
_handle.startDrag(
false
,
new
Rectangle (
0
,
0
,
0
, _height - _handleHeight ));
?}
?
private
function
MouseMoveHandler( e:MouseEvent ):
void
{
?updateValue();
?}
public
function
invalidate():
void
{
?draw();
?}
?public
function
move( x:
Number
, y:
Number
):
void
{
?this
.x=x;
?this
.y=y;
?}
?public
function
setSize( w:
Number
, h:
Number
):
void
{
?_width=w;
?_height=h;
?draw();
}
?public
function
set
backBorderColor( n:
uint
):
void
{
?_backBorderColor=n;
?draw();
?}
?
public
function
get
backBorderColor():
uint
{
?return
_backBorderColor;
?}
?public
function
set
backColor( n:
uint
):
void
{
_backColor=n;
?draw();
?}
?public
function
get
backColor():
uint
{
return
_backColor;
?}
public
function
set
backRadius( n:
Number
):
void
{
?_backRadius=n;
?}
?public
function
get
backRadius():
Number
{
?return
_backRadius;
?}
?public
function
set
backWidth( n:
Number
):
void
{
?_backWidth=n;
?draw();
?}
?public
function
get
backWidth():
Number
{
?return
_backWidth;
?}
?public
function
set
handleBorderColor( n:
uint
):
void
{
?_handleBorderColor=n;
?draw();
?}
?public
function
get
handleBorderColor():
uint
{
?return
_handleBorderColor;
?}
?public
function
set
handleColor( n:
uint
):
void
{
_handleColor=n;
?draw();
?}
public
function
get
handleColor():
uint
{
return
_handleColor;
?}
?public
function
set
handleRadius( n:
Number
):
void
{
?_handleRadius=n;
?draw();
?}
public
function
get
handleRadius():
Number
{
?return
_handleRadius;
?}
?public
function
set
handleHeight( n:
Number
):
void
{
?_handleHeight=n;
draw();
?updatePosition();
?}
?public
function
get
handleHeight():
Number
{
?return
_handleHeight;
?}
?override
public
function
set
height( n:
Number
):
void
{
?_height=n;
?draw();
?}
?override
public
function
get
height():
Number
{
?return
_height;
?}
?public
function
set
max( n:
Number
):
void
{
?_max=n;
?updatePosition();
}
?public
function
get
max():
Number
{
return
_max;
?}
?public
function
set
min( n:
Number
):
void
{
_min=n;
?updatePosition();
}
?public
function
get
min():
Number
{
?return
_min;
?}
?public
function
set
value( n:
Number
):
void
{
value=n;
?value=Math.min(_max,Math.max(_value,_min));
?updatePosition();
?}
?public
function
get
value():
Number
{
?return
_value;
?}
?override
public
function
set
width( n:
Number
):
void
{
_width=n;
?draw();
?}
?override
public
function
get
width():
Number
{
?return
_width;
}
?}
?}
基本测试:
var
segment:Segment=
new
Segment(
100
,
20
);
?addChild(segment);
segment.x=
50
;
?segment.y=
120
;
?var
slider:SimpleSlider=
new
SimpleSlider(-
90
,
90
,
0
);
?addChild(slider);
?slider.x=
200
;
?slider.y=
70
;
slider.addEventListener(Event.CHANGE,onChange);
?function
onChange(event:Event):
void
{
?segment.rotation=slider.value;
?}
双关节运动测试:
package
{
?import
flash.display.Sprite;
?import
flash.events.Event;
?public
class
TwoSegments
extends
Sprite {
private
var
slider0:SimpleSlider;???????
?private
var
slider1:SimpleSlider;
?private
var
segment0:Segment;
?private
var
segment1:Segment;
?public
function
TwoSegments() {
?init();
?}
?private
function
init():
void
{
?segment0=
new
Segment(
100
,
20
);
?addChild(segment0);
?segment0.x=
50
;
?segment0.y=
150
;
?segment1=
new
Segment(
100
,
20
);
addChild(segment1);
?//关键:segment1的固定端连接到segment0的自由端
segment1.x=segment0.getPin().x;
?segment1.y=segment0.getPin().y;
?slider0=
new
SimpleSlider(-
90
,
90
,
0
);
addChild(slider0);
?slider0.x=
320
;
?slider0.y=
90
;
?slider0.addEventListener(Event.CHANGE,onChange);
?slider1=
new
SimpleSlider(-
90
,
90
,
0
);
?addChild(slider1);
slider1.x=
340
;
?slider1.y=
90
;
?slider1.addEventListener(Event.CHANGE,onChange);
?}
?private
function
onChange(event:Event):
void
{
?segment0.rotation=slider0.value;
?segment1.rotation=slider1.value;
?segment1.x=segment0.getPin().x;
?segment1.y=segment0.getPin().y;
?}
?}
}
如果把segment0与segment1分别看做人的胳膊与手臂,上面这个示例显然有二个地方不自然:
1.没有人的(前)手臂向下做-90度的弯曲(除非脱臼)
2.人的上肢整体向上抬时,手臂会随着胳膊一起绕肩关节向上旋转,而不应该一直固定于某个角度
修正的方法很简单,onChange改成下面这样:
?private
function
onChange(event:Event):
void
{
?segment0.rotation=slider0.value;
?segment1.rotation=slider1.value + segment0.rotation;
//注意这行
?segment1.x=segment0.getPin().x;
?segment1.y=segment0.getPin().y;
?}
同时限制一下slider1的角度范围,改成下面这样:
slider1=
new
SimpleSlider(-
160
,
0
,
0
);
单腿原地“踢”模拟
?package
{
?import
flash.display.Sprite;
import
flash.events.Event;
?public
class
Walking1
extends
Sprite {
?private
var
segment0:Segment;
?private
var
segment1:Segment;
?private
var
cycle:
Number
=
0
;
?private
var
offset:
Number
= -Math.PI/
2
;
//小腿的运动看上去应该滞后于大腿,所以需要加入反向偏移量
?public
function
Walking1() {
?init();
?trace
(Math.PI/
180
);
?trace
(
0.05
*
180
/Math.PI);
?}
?private
function
init():
void
{
?segment0=
new
Segment(
100
,
20
);
?addChild(segment0);
segment0.x=
200
;
segment0.y=
200
;
?segment1=
new
Segment(
100
,
20
);
?addChild(segment1);
segment1.x=segment0.getPin().x;
?segment1.y=segment0.getPin().y;
?addEventListener(Event.ENTER_FRAME,onEnterFrame);
?}
private
function
onEnterFrame(event:Event):
void
{
?cycle+=.
05
;
?var
angle0:
Number
=Math.sin(cycle)*
45
+
90
;
//-45到45整体加上90度以后,就变成45到135,即:大腿垂直方向左右摆动45度
?var
angle1:
Number
= Math.sin(cycle + offset) *
45
+
45
;
//即:小腿相对大腿末端做0-90度的正向旋转。建议大家尝试修改一下这里的+45值的大小,看看效果有什么不同
?segment0.rotation=angle0;
?segment1.rotation=segment0.rotation+angle1;
?segment1.x=segment0.getPin().x;
segment1.y=segment0.getPin().y;
?}
?}
?}
双腿原地行走:
package
{
?import
flash.display.Sprite;
?import
flash.events.Event;
public
class
Walking4
extends
Sprite {
private
var
segment0:Segment;
?private
var
segment1:Segment;
?private
var
segment2:Segment;
?private
var
segment3:Segment;
?private
var
cycle:
Number
=
0
;
?private
var
offset:
Number
=- Math.PI/
2
;
//小腿的运动看上去应该滞后于大腿,所以需要加入反向偏移量
?public
function
Walking4() {
?init();?????????
?}
?private
function
init():
void
{
?segment0=
new
Segment(
100
,
35
);
//第一条大腿
addChild(segment0);
?segment0.x=
200
;
?segment0.y=
50
;
?segment1=
new
Segment(
100
,
20
);???????????
?addChild(segment1);
?segment1.x=segment0.getPin().x;
//第一条小腿连接到第一条大腿
?segment1.y=segment0.getPin().y;
?segment2=
new
Segment(
100
,
35
);
//第二条大腿
?segment2.x = segment0.x;
//第二条大腿与第一条大腿坐标相同,视觉效果上看,就象都固定在腰部
?segment2.y = segment0.y;
addChild(segment2);
segment3=
new
Segment(
100
,
20
);???????????
?addChild(segment3);
?segment3.x=segment2.getPin().x;
//第二条小腿连接到第二条大腿
?segment3.y=segment2.getPin().y;
?addEventListener(Event.ENTER_FRAME,EnterFrameHandler);
?}
?private
function
EnterFrameHandler(event:Event):
void
{
?walk(segment0, segment1, cycle);?
?walk(segment2, segment3, cycle + Math.PI);
//注意这里的:+Math.PI,如果不加这个,二条腿的频率/角度完全相同,将重叠在一起,加上180度以后,正好反相过来,一条腿在前,另一条腿在后
cycle += .
05
;
?}
//把"走"的动作封装起来
?private
function
walk(segA:Segment, segB:Segment, cyc:
Number
):
void
{
?var
angleA:
Number
=Math.sin(cyc)*
45
+
90
;
?var
angleB:
Number
=Math.sin(cyc+offset)*
45
+
45
;
?segA.rotation=angleA;
?segB.rotation=segA.rotation+angleB;
?segB.x=segA.getPin().x;
?segB.y=segA.getPin().y;
?}
?}
?}
加入滑块控制条后的样子:
?package
{
?import
flash.display.Sprite;
?import
flash.events.Event;
?public
class
Walking5
extends
Sprite {
private
var
segment0:Segment;
?private
var
segment1:Segment;
private
var
segment2:Segment;
?private
var
segment3:Segment;
?private
var
speedSlider:SimpleSlider;
private
var
thighRangeSlider:SimpleSlider;
?private
var
thighBaseSlider:SimpleSlider;
?private
var
calfRangeSlider:SimpleSlider;
private
var
calfOffsetSlider:SimpleSlider;
private
var
cycle:
Number
=
0
;
public
function
Walking5() {
?init();
?}
?private
function
init():
void
{
?segment0=
new
Segment(
100
,
30
);
addChild(segment0);
segment0.x=
200
;
?segment0.y=
100
;
?segment1=
new
Segment(
100
,
20
);
addChild(segment1);
?segment1.x=segment0.getPin().x;
?segment1.y=segment0.getPin().y;
?segment2=
new
Segment(
100
,
30
);
?addChild(segment2);
?segment2.x=
200
;
segment2.y=
100
;
?segment3=
new
Segment(
100
,
20
);
?addChild(segment3);
?segment3.x=segment2.getPin().x;
segment3.y=segment2.getPin().y;
?//控制速度的滑块
?speedSlider=
new
SimpleSlider(
0
,
0.5
,
0.11
);
?addChild(speedSlider);
?speedSlider.x=
10
;
?speedSlider.y=
10
;
?//控制大腿能分开的最大角度
?thighRangeSlider=
new
SimpleSlider(
0
,
90
,
45
);
?addChild(thighRangeSlider);
?thighRangeSlider.x=
30
;
thighRangeSlider.y=
10
;
?//大腿旋转的偏移量
?thighBaseSlider=
new
SimpleSlider(
0
,
180
,
90
);
addChild(thighBaseSlider);??????????
?thighBaseSlider.x=
50
;
?thighBaseSlider.y=
10
;
?//小腿旋转的偏移量
?calfRangeSlider=
new
SimpleSlider(
0
,
90
,
45
);
?addChild(calfRangeSlider);??????????
?calfRangeSlider.x=
70
;
?calfRangeSlider.y=
10
;
//小腿相对大腿滞后的偏移量
?calfOffsetSlider=
new
SimpleSlider(-
3.14
,
3.14
,-
1.57
);
?addChild(calfOffsetSlider);?????????
calfOffsetSlider.x=
90
;
?calfOffsetSlider.y=
10
;
?addEventListener(Event.ENTER_FRAME, EnterFrameHandler);
?}
private
function
EnterFrameHandler(e:Event):
void
{
walk(segment0, segment1, cycle);
?walk(segment2, segment3, cycle + Math.PI);
cycle+=speedSlider.value;
?}
?private
function
walk(segA:Segment, segB:Segment,cyc:
Number
):
void
{
?var
angleA:
Number
= Math.sin(cyc) * thighRangeSlider.value + thighBaseSlider.value;
?var
angleB:
Number
= Math.sin(cyc +calfOffsetSlider.value) * calfRangeSlider.value + calfRangeSlider.value;
?segA.rotation=angleA;
?segB.rotation=segA.rotation+angleB;
?segB.x=segA.getPin().x;
segB.y=segA.getPin().y;
?}
?}
?}
真正的行走: