Трехмерная анимашку с костями во флеш.

Читал книжецу "Исскуство програмирования игр на С++" Михаил Фленов. Радует, что пишут ребятки в нашем отечестве про программирование и все-такое... В общем там есть раздел про кости, ну их надо только вертеть, а не перемещать. Мне как-бы кажется что C++ и ActionScript, как-бы похожи... Было бы проще если-бы синтакис AS3 был не ECMA подобный, а Си подобный. Есть первая кость, к ее концу прикреплана вторая, ну и так дальше (к концу может прикреплятся несколько костей {children} в редакторе блендер. Я наваял такой класс для кости:
package lex.m 
{
	import flash.geom.*;
	/**
	 * ...
	 * @author Alex Lexcuk http://www.murmadillo.tut.su
	 */
	public class Bone
	{
		public var name:String;
		public var parent:Bone;
		public var children:Vector.<Bone>;
		public var localM3d:Matrix3D = new Matrix3D();
		public var globalM3d:Matrix3D = new Matrix3D();
		public var head:Vector.<Number>;
		public var tail:Vector.<Number>;
		public var tempHead:Vector.<Number> = new Vector.<Number>();
		public var tempTail:Vector.<Number> = new Vector.<Number>();
		
		public var strParent:String;
		public var strChildrens:Vector.<String>;
		
		public var vec:Vector.<Number> = new Vector.<Number>();
		public var ind:Vector.<int> = new Vector.<int>();
		public var temp:Vector.<Number> = new Vector.<Number>();
		
		public function Bone() 
		{
			
		}
		
	}

}

Он такой большой, потому-что используются промежуточные заполнения для изготовления из множества костей арматуры и как-бы первоначальной сборки скелета из XML структуры, которая была получена экспортом из блендера
Так-что созрел и класс для арматуры
package lex.m 
{
	import flash.display.Graphics;
	import flash.geom.Matrix3D;
	/**
	 * ...
	 * @author Alex Lexcuk http://www.murmadillo.tut.su
	 */
	public class Armature
	{
		public var bones:Vector.<Bone>;
		public var nav:Object;
		public var parentBone:Bone;
		public function Armature() 
		{
			bones = new Vector.<Bone>();
			nav = new Object();
		}

		public function makeBase(vec:Vector.<Number>):void {
			var i:int;
			var j:int;
			var len:int;
			var lenj:int;
			var s:String;
			var si:int;
			var c:int;
			var vc:int;
			len = bones.length;
			for (i = 0; i < len; i++) {
				nav[bones[i].name] = i;
				if (bones[i].strParent == 'None') parentBone = bones[i];
			}
			
			for (i = 0; i < len; i++) {
				//заполнение костей векторами
				for (c = 0; c < bones[i].ind.length; c++) {
					vc = bones[i].ind[c];
					bones[i].vec.push(vec[vc * 3], vec[vc * 3 + 1], vec[vc * 3 + 2]);
				}
				
				if (bones[i].strChildrens[0] != 'None') {
					bones[i].children = new Vector.<Bone>();
					lenj = bones[i].strChildrens.length;
					for (j = 0; j < lenj; j++) {
						s = bones[i].strChildrens[j];
						si = nav[s];
						bones[i].children.push(bones[si]);
					}
				}
				
				if (bones[i].strParent != 'None') {
					bones[i].parent = bones[ nav[bones[i].strParent] ];
				}
			}
		}

		public function compilate(b:Bone):void {
			var i:int;
			
			
			b.globalM3d.identity();
			
			if (b.parent != null) b.globalM3d.append(b.parent.globalM3d);
			
			b.globalM3d.appendTranslation(-b.tempHead[0], -b.tempHead[1], -b.tempHead[2]);
			b.globalM3d.append(b.localM3d);
			b.globalM3d.appendTranslation( b.tempHead[0], b.tempHead[1], b.tempHead[2]);
			
			b.globalM3d.transformVectors(b.tempTail, b.tempTail);
			
			
			if (b.children != null) for (i = 0; i < b.children.length; i++) {
				b.globalM3d.transformVectors(b.children[i].head, b.children[i].tempHead);
				//b.children[i].tempHead = b.tempTail.concat();
				b.globalM3d.transformVectors(b.children[i].tail, b.children[i].tempTail);
				compilate(b.children[i]);
			}
			
			
		}

		public function showBone(g:Graphics,m3d:Matrix3D,focus:Number):void {
			var tail:Vector.<Number> = new Vector.<Number>();
			var head:Vector.<Number> = new Vector.<Number>();
			var i:int;
			var len:int;
			var m:Matrix3D;
			var b:Bone;
			var z:Number;
			var w:Number;
			
			len = bones.length;
			for (i = 0; i < len; i++) {
				b = bones[i];
				m = b.globalM3d;
				m.append(m3d);
				m.transformVectors(b.head, head);
				m.transformVectors(b.tail, tail);
				
				//m3d.transformVectors(b.tempHead, head);
				//m3d.transformVectors(b.tempTail, tail);
				
				//head = b.tempHead.concat();
				//tail = b.tempTail.concat();
				
				z = head[2];//z
				w = 1 / ((focus + z) / focus);
				head[0] *= w;//x
				head[1] *= w;//y
				head[2] *= w;//z
				
				z = tail[2];//z
				w = 1 / ((focus + z) / focus);
				tail[0] *= w;//x
				tail[1] *= w;//y
				tail[2] *= w;//z
				
				g.lineStyle(1, 0x00FF80);
				g.drawCircle(head[0], head[1], 4);

				g.lineStyle(1.5, 0xFFFF80);
				g.drawCircle(tail[0], tail[1], 2);

				g.lineStyle(1, 0xFFFF00);
				g.moveTo(head[0], head[1]);
				g.lineTo(tail[0], tail[1]);
				
			}
		}

	}

}

Метод makeBase собырает из сырых костей арматуру и заполняет дополнительные поля в костях, прописывает им parent и children
Метод compilate вызывает сам себя, те. вызывается рекурсивно, матрица кости работает относительно head основания кости и подключает в себя localM3d (localM3d содержит в себе только ротации) потом полученную матрицу globalM3d передает дольше своим деткам (children) и их опять вызывает compilate.
Управляет всем этим добром, у меня (ну так получилось...) еще один класс BoneParam.as
package lex.m 
{
	import flash.display.Graphics;
	import flash.geom.*;
	/**
	 * ...
	 * @author Alex Lexcuk http://www.murmadillo.tut.su
	 */
	public class BoneParam
	{
		private var boneXml:XML = 
		<doc>
			<bone name = 'Bone.006' parent = 'Bone.005' children = 'None' head = '32.239933,-0.000003,0.250367' tail = '43.937923,-0.000004,0.250367' />
			<bone name = 'Bone.001' parent = 'Bone' children = 'Bone.002' head = '-27.077045,0.000001,0.250366' tail = '-15.024578,0.000000,0.250367' />
			<bone name = 'Bone.002' parent = 'Bone.001' children = 'Bone.003' head = '-15.024578,0.000000,0.250367' tail = '-2.617661,-0.000001,0.250367' />
			<bone name = 'Bone.003' parent = 'Bone.002' children = 'Bone.004' head = '-2.617661,-0.000001,0.250367' tail = '9.671078,-0.000002,0.250367' />
			<bone name = 'Bone.004' parent = 'Bone.003' children = 'Bone.005' head = '9.671078,-0.000002,0.250367' tail = '21.841713,-0.000002,0.250367' />
			<bone name = 'Bone.005' parent = 'Bone.004' children = 'Bone.006' head = '21.841713,-0.000002,0.250367' tail = '32.239933,-0.000003,0.250367' />
			<bone name = 'Bone' parent = 'None'children = 'Bone.001' head = '-37.357090,0.000000,0.234203' tail = '-27.077045,0.000001,0.250366' />

			<group name='Bone' ind='2,3,6,7,8,9,10,11,36,37,38,39'/>
			<group name='Bone.001' ind='12,13,14,15,40,41,42,43,44,45,46,47'/>
			<group name='Bone.002' ind='16,17,18,19,20,21,22,23,48,49,50,51'/>
			<group name='Bone.003' ind='20,21,22,23,24,25,26,27,52,53,54,55'/>
			<group name='Bone.004' ind='24,25,26,27,28,29,30,31,56,57,58,59'/>
			<group name='Bone.005' ind='28,29,30,31,60,61,62,63'/>
			<group name='Bone.006' ind='0,1,4,5,32,33,34,35,64,65,66,67'/>



		</doc>

		public var boneInd:Vector.<Vector.<int>>;
		public var boneHead:Vector.<Number>;
		public var boneTail:Vector.<Number>;
		public var boneName:Vector.<String>;
		
		public var headTemp:Vector.<Number>;
		public var tailTemp:Vector.<Number>;
		public var armature:Armature;
		
		
		public function BoneParam(vec:Vector.<Number>) 
		{
			boneInd = new Vector.<Vector.<int>>();
			boneName = new Vector.<String>();
			boneHead = new Vector.<Number>();
			boneTail = new Vector.<Number>();
			armature = new Armature();
			var i:int;
			var s:String;
			var arr:Array;
			var b:Bone;
			var obj:Object = new Object();
			for (i=0; i<boneXml.group.length(); i++){
				s = boneXml.group[i].attribute("ind");
				arr = s.split(',');
				boneInd.push(Vector.<int>(arr));
				boneName.push(boneXml.group[i].attribute("name"))
				obj[boneXml.group[i].attribute("name")] = Vector.<int>(arr);
			}
			
			for (i = 0; i < boneXml.bone.length(); i++) {
				//заполнение арматуры
				armature.bones.push(b = new Bone());
				
				
				
				s = boneXml.bone[i].attribute("head");
				arr = s.split(',');
				boneHead.push(arr[0], arr[1], arr[2]);
				b.head = Vector.<Number>(arr);
				
				s = boneXml.bone[i].attribute("tail");
				arr = s.split(',');
				boneTail.push(arr[0], arr[1], arr[2]);
				b.tail = Vector.<Number>(arr);
				
				
				b.name = boneXml.bone[i].attribute("name");
				
				b.ind = obj[b.name];
				
				b.strParent = boneXml.bone[i].attribute("parent");
				
				s = boneXml.bone[i].attribute("children");
				arr = s.split(',');
				
				b.strChildrens = Vector.<String>(arr);
			}
			
			trace(boneHead.length);
			trace(boneTail.length);
			headTemp = new Vector.<Number>();
			tailTemp = new Vector.<Number>();
			
			armature.makeBase(vec);
			var head:Vector3D = new Vector3D(armature.parentBone.head[0], armature.parentBone.head[1], armature.parentBone.head[2]);
			//armature.parentBone.localM3d.appendTranslation(armature.parentBone.head[0], armature.parentBone.head[1], armature.parentBone.head[2]);
			
			//armature.parentBone.localM3d.appendRotation(30, Vector3D.Z_AXIS);
			
			//armature.parentBone.localM3d.appendTranslation( -head.x, -head.y, -head.z);
			armature.bones[1].localM3d.appendRotation(10, Vector3D.Z_AXIS);
			armature.bones[0].localM3d.appendRotation(90, Vector3D.Z_AXIS);
		}

		public function boneApplyM3dTemp(m3d:Matrix3D, focus:Number, g:Graphics ):void {
			
			m3d.transformVectors(boneHead, headTemp);
			m3d.transformVectors(boneTail, tailTemp);
			var len:int;
			var i:int;
			var z:Number;
			var w:Number;
			len = headTemp.length;
			for (i = 0; i < len; i+=3) {
				z = headTemp[i + 2 ];//z
				w = 1 / ((focus + z) / focus);
				headTemp[i] *= w;//x
				headTemp[i + 1] *= w;//y
				headTemp[i + 2] *= w;//z
				
				z = tailTemp[i + 2 ];//z
				w = 1 / ((focus + z) / focus);
				
				tailTemp[i] *= w;//x
				tailTemp[i + 1] *= w;//y
				tailTemp[i + 2] *= w;//z
			}
			
			armature.parentBone.tempHead = armature.parentBone.head.concat();
			armature.parentBone.tempTail = armature.parentBone.tail.concat();
			armature.compilate(armature.parentBone);
			armature.showBone(g, m3d, focus);
		}

		public function showBones(g:Graphics):void {
			g.clear();
			var i:int;
			var len:int = boneInd.length;
			for (i = 0; i < len; i++) {
				g.lineStyle(1,0x80FFFF);
				g.drawCircle(headTemp[i * 3], headTemp[i * 3 + 1], 3);
				g.moveTo(headTemp[i * 3], headTemp[i * 3 + 1]);
				g.lineStyle(1,0xFF0080);
				g.lineTo(tailTemp[i * 3], tailTemp[i * 3 + 1]);
			}
		}

	}

}

Ну с ним все должно быть понятно... Он как промежуточное звено между визуализацией (просто показывает кости) и (не смейтесь. базовой XML) ну кароче перепутано все пипец как. Код отображения модели вят с вывода 3D логотипа с небольшими изменениями. Да!!! Пока не забыл, кости по идее должны воздействовать на вершины сетки долями и каждая кость по идее должна иметь долю воздействия на вершину (я не понял как это сделать в блендере, но даже если бы и понял, то тогда-бы все равно не понятно как это свойство реализовать во флеш {я догадываюсь, но это сознательное уменьшение FPS} через сильно большую нагрузку на процесор). Теперь имея кости надо не применять матрицу ко все вершинам объекта, а применять матрицы костей к группам вершин, которые прописаны в костях, затем надо из этих групп опять собрать массив с векторами. Вот так это реализовано
			//забиваем массив с вектором из преобразованных костей
			var blen:int;
			var bvout:Vector.<Number>;
			var bout:Vector.<Number>;
			var bones:Vector.<Bone>;
			var bind:Vector.<int>;
			var p:int;
			bones = boneParam.armature.bones;
			blen = bones.length;
			for (i = 0; i < blen; i++) {
				bvout = bones[i].vec;
				bout = bones[i].temp;
				bones[i].globalM3d.transformVectors(bvout, bout);
				bind = bones[i].ind;
				len = bind.length;
				p = 0;
				for (c = 0; c < len; c++) {
					dats[bind[c] * 3] = bout[p++];
					dats[bind[c] * 3+1] = bout[p++];
					dats[bind[c] * 3+2] = bout[p++];
				}
			}
			//все забили на массив с вектором из преобразованных костей

Вроде все вот.

дема
Единственное почему-то перепутались Bone и Bone.006. Bone это главная кость, а Bone.006 послядняя кость, а меня почему-то когда вращается Bone.006 вращается и весь скелет. Принцип от этого не меняется. Если что архив к проекту.