zoukankan      html  css  js  c++  java
  • Flash/Flex学习笔记(46):正向运动学

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

    先来一个基本的关节类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 _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;
    		}
    	}
    }
    

    真正的行走:

    package {
    	import flash.display.Sprite;
    	import flash.display.StageScaleMode;
    	import flash.display.StageAlign;
    	import flash.display.Stage;
    	import flash.events.Event;
    	import flash.geom.Point;
    	public class RealWalk extends Sprite {
    		
    		private var segment0:Segment;//大腿1
    		private var segment1:Segment;//小腿1
    		private var segment2:Segment;//大腿2
    		private var segment3:Segment;//小腿2
    		
    		//各控制滑块
    		private var speedSlider:SimpleSlider;
    		private var thighRangeSlider:SimpleSlider;
    		private var thighBaseSlider:SimpleSlider;
    		private var calfRangeSlider:SimpleSlider;
    		private var calfOffsetSlider:SimpleSlider;
    		private var gravitySlider:SimpleSlider;
    		
    		private var cycle:Number=0;
    		private var vx:Number=0;
    		private var vy:Number=0;
    
    		public function RealWalk() {
    			init();
    		}
    
    		private function init():void {
    			stage.scaleMode = StageScaleMode.NO_SCALE;
    			stage.align = StageAlign.TOP_LEFT;
    			segment0=new Segment(50,15);
    			addChild(segment0);
    			segment0.x=200;
    			segment0.y=100;
    			
    			segment1=new Segment(50,10);
    			addChild(segment1);
    			segment1.x=segment0.getPin().x;
    			segment1.y=segment0.getPin().y;
    			
    			segment2=new Segment(50,15);
    			addChild(segment2);
    			segment2.x=200;
    			segment2.y=100;
    			
    			segment3=new Segment(50,10);
    			addChild(segment3);
    			segment3.x=segment2.getPin().x;
    			segment3.y=segment2.getPin().y;
    			
    			speedSlider=new SimpleSlider(0,0.3,0.12);
    			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;
    			
    			gravitySlider=new SimpleSlider(0,1,0.2);
    			addChild(gravitySlider);
    			gravitySlider.x=110;
    			gravitySlider.y=10;
    			
    			addEventListener(Event.ENTER_FRAME, EnterFrameHandler);
    		}
    		
    		private function EnterFrameHandler(event:Event):void {
    			doVelocity();
    			walk(segment0, segment1, cycle);
    			walk(segment2, segment3, cycle + Math.PI);
    			cycle+=speedSlider.value;
    			checkFloor(segment1);
    			checkFloor(segment3);
    			checkWalls();
    		}
    		
    		//行走姿态的处理
    		private function walk(segA:Segment, segB:Segment,cyc:Number):void {
    			var foot:Point=segB.getPin();
    			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;
    			segB.vx=segB.getPin().x-foot.x;
    			segB.vy=segB.getPin().y-foot.y;
    		}
    		
    		//下肢的速度处理
    		private function doVelocity():void {
    			vy+=gravitySlider.value;
    			//因为小腿是跟着大腿的,所以只要处理大腿的速度即可
    			segment0.x+=vx;
    			segment0.y+=vy;
    			segment2.x+=vx;
    			segment2.y+=vy;
    		}
    		
    		private function checkFloor(seg:Segment):void {
    			var yMax:Number=seg.getBounds(this).bottom;
    			//如果最下面的小腿超出了舞台下边界
    			if (yMax>stage.stageHeight) {
    				var dy:Number=yMax-stage.stageHeight;
    				//将所有的关节(大腿和小腿)全部上移,以防止两条腿超出舞台下边界
    				segment0.y-=dy;
    				segment1.y-=dy;
    				segment2.y-=dy;
    				segment3.y-=dy;
    				//速度反弹
    				vx-=seg.vx;
    				vy-=seg.vy;
    			}
    		}
    		
    		//屏幕环绕
    		private function checkWalls():void {
    			var w:Number=stage.stageWidth+200;
    			if (segment0.x>stage.stageWidth+100) {
    				segment0.x-=w;
    				segment1.x-=w;
    				segment2.x-=w;
    				segment3.x-=w;
    			} else if (segment0.x < 100*-1) {
    				segment0.x+=w;
    				segment1.x+=w;
    				segment2.x+=w;
    				segment3.x+=w;
    			}
    		}
    	}
    }
    
    作者:菩提树下的杨过
    出处:http://yjmyzz.cnblogs.com
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    一周总结
    [z]OpenGL Wiki
    [Z]OpenCL Data Parallel Primitives Library
    [z]苹果用OpenCL实现的Parallel Prefix Sum
    指定VC中std::sort的比较函数时发生"invalid operator<"错误原因
    [z]FNV哈希算法
    [z]NViDIA用OpenCL实现的很多基础并行算法
    [z]一个基于CUDA的基础并行算法库
    [z]一个讲解很多OpenGL中基本概念的网站
    [Z]Marching Cubes的实现
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/1725111.html
Copyright © 2011-2022 走看看