所谓"正向运动学"通俗点讲就是把几个连接部件的一端固定起来,另一个端可以自由(向前/向外)运动。比如人的行走,单个下肢可以理解为脚连接小腿,小腿连接大腿,大腿连接腰。行走的过程,相当于二条腿相对固定于腰部,大腿运动驱动小腿,小腿又驱动脚,从而带动整个连接系统的一系列运动。
先来一个基本的关节类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;
}
}
}
真正的行走: