babylon.skeleton.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. module BABYLON {
  2. export class Skeleton {
  3. public bones = new Array<Bone>();
  4. public dimensionsAtRest: Vector3;
  5. public needInitialSkinMatrix = false;
  6. private _scene: Scene;
  7. private _isDirty = true;
  8. private _transformMatrices: Float32Array;
  9. private _meshesWithPoseMatrix = new Array<AbstractMesh>();
  10. private _animatables: IAnimatable[];
  11. private _identity = Matrix.Identity();
  12. private _ranges: { [name: string]: AnimationRange; } = {};
  13. constructor(public name: string, public id: string, scene: Scene) {
  14. this.bones = [];
  15. this._scene = scene;
  16. scene.skeletons.push(this);
  17. //make sure it will recalculate the matrix next time prepare is called.
  18. this._isDirty = true;
  19. }
  20. // Members
  21. public getTransformMatrices(mesh: AbstractMesh): Float32Array {
  22. if (this.needInitialSkinMatrix && mesh._bonesTransformMatrices) {
  23. return mesh._bonesTransformMatrices;
  24. }
  25. return this._transformMatrices;
  26. }
  27. public getScene(): Scene {
  28. return this._scene;
  29. }
  30. // Methods
  31. /**
  32. * @param {boolean} fullDetails - support for multiple levels of logging within scene loading
  33. */
  34. public toString(fullDetails?: boolean): string {
  35. var ret = `Name: ${this.name}, nBones: ${this.bones.length}`;
  36. ret += `, nAnimationRanges: ${this._ranges ? Object.keys(this._ranges).length : "none"}`;
  37. if (fullDetails) {
  38. ret += ", Ranges: {";
  39. let first = true;
  40. for (let name in this._ranges) {
  41. if (first) {
  42. ret += ", ";
  43. first = false;
  44. }
  45. ret += name;
  46. }
  47. ret += "}";
  48. }
  49. return ret;
  50. }
  51. /**
  52. * Get bone's index searching by name
  53. * @param {string} name is bone's name to search for
  54. * @return {number} Indice of the bone. Returns -1 if not found
  55. */
  56. public getBoneIndexByName(name: string): number {
  57. for (var boneIndex = 0, cache = this.bones.length; boneIndex < cache; boneIndex++) {
  58. if (this.bones[boneIndex].name === name) {
  59. return boneIndex;
  60. }
  61. }
  62. return -1;
  63. }
  64. public createAnimationRange(name: string, from: number, to: number): void {
  65. // check name not already in use
  66. if (!this._ranges[name]) {
  67. this._ranges[name] = new AnimationRange(name, from, to);
  68. for (var i = 0, nBones = this.bones.length; i < nBones; i++) {
  69. if (this.bones[i].animations[0]) {
  70. this.bones[i].animations[0].createRange(name, from, to);
  71. }
  72. }
  73. }
  74. }
  75. public deleteAnimationRange(name: string, deleteFrames = true): void {
  76. for (var i = 0, nBones = this.bones.length; i < nBones; i++) {
  77. if (this.bones[i].animations[0]) {
  78. this.bones[i].animations[0].deleteRange(name, deleteFrames);
  79. }
  80. }
  81. this._ranges[name] = undefined; // said much faster than 'delete this._range[name]'
  82. }
  83. public getAnimationRange(name: string): AnimationRange {
  84. return this._ranges[name];
  85. }
  86. /**
  87. * Returns as an Array, all AnimationRanges defined on this skeleton
  88. */
  89. public getAnimationRanges(): AnimationRange[] {
  90. var animationRanges: AnimationRange[] = [];
  91. var name: string;
  92. var i: number = 0;
  93. for (name in this._ranges) {
  94. animationRanges[i] = this._ranges[name];
  95. i++;
  96. }
  97. return animationRanges;
  98. }
  99. /**
  100. * note: This is not for a complete retargeting, only between very similar skeleton's with only possible bone length differences
  101. */
  102. public copyAnimationRange(source: Skeleton, name: string, rescaleAsRequired = false): boolean {
  103. if (this._ranges[name] || !source.getAnimationRange(name)) {
  104. return false;
  105. }
  106. var ret = true;
  107. var frameOffset = this._getHighestAnimationFrame() + 1;
  108. // make a dictionary of source skeleton's bones, so exact same order or doublely nested loop is not required
  109. var boneDict = {};
  110. var sourceBones = source.bones;
  111. var nBones: number;
  112. var i: number;
  113. for (i = 0, nBones = sourceBones.length; i < nBones; i++) {
  114. boneDict[sourceBones[i].name] = sourceBones[i];
  115. }
  116. if (this.bones.length !== sourceBones.length) {
  117. Tools.Warn(`copyAnimationRange: this rig has ${this.bones.length} bones, while source as ${sourceBones.length}`);
  118. ret = false;
  119. }
  120. for (i = 0, nBones = this.bones.length; i < nBones; i++) {
  121. var boneName = this.bones[i].name;
  122. var sourceBone = boneDict[boneName];
  123. if (sourceBone) {
  124. ret = ret && this.bones[i].copyAnimationRange(sourceBone, name, frameOffset, rescaleAsRequired);
  125. } else {
  126. Tools.Warn("copyAnimationRange: not same rig, missing source bone " + boneName);
  127. ret = false;
  128. }
  129. }
  130. // do not call createAnimationRange(), since it also is done to bones, which was already done
  131. var range = source.getAnimationRange(name);
  132. this._ranges[name] = new AnimationRange(name, range.from + frameOffset, range.to + frameOffset);
  133. return ret;
  134. }
  135. public returnToRest(): void {
  136. for (var index = 0; index < this.bones.length; index++) {
  137. this.bones[index].returnToRest();
  138. }
  139. }
  140. private _getHighestAnimationFrame(): number {
  141. var ret = 0;
  142. for (var i = 0, nBones = this.bones.length; i < nBones; i++) {
  143. if (this.bones[i].animations[0]) {
  144. var highest = this.bones[i].animations[0].getHighestFrame();
  145. if (ret < highest) {
  146. ret = highest;
  147. }
  148. }
  149. }
  150. return ret;
  151. }
  152. public beginAnimation(name: string, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void): Animatable {
  153. var range = this.getAnimationRange(name);
  154. if (!range) {
  155. return null;
  156. }
  157. return this._scene.beginAnimation(this, range.from, range.to, loop, speedRatio, onAnimationEnd);
  158. }
  159. public _markAsDirty(): void {
  160. this._isDirty = true;
  161. }
  162. public _registerMeshWithPoseMatrix(mesh: AbstractMesh): void {
  163. this._meshesWithPoseMatrix.push(mesh);
  164. }
  165. public _unregisterMeshWithPoseMatrix(mesh: AbstractMesh): void {
  166. var index = this._meshesWithPoseMatrix.indexOf(mesh);
  167. if (index > -1) {
  168. this._meshesWithPoseMatrix.splice(index, 1);
  169. }
  170. }
  171. public _computeTransformMatrices(targetMatrix: Float32Array, initialSkinMatrix: Matrix): void {
  172. for (var index = 0; index < this.bones.length; index++) {
  173. var bone = this.bones[index];
  174. var parentBone = bone.getParent();
  175. if (parentBone) {
  176. bone.getLocalMatrix().multiplyToRef(parentBone.getWorldMatrix(), bone.getWorldMatrix());
  177. } else {
  178. if (initialSkinMatrix) {
  179. bone.getLocalMatrix().multiplyToRef(initialSkinMatrix, bone.getWorldMatrix());
  180. } else {
  181. bone.getWorldMatrix().copyFrom(bone.getLocalMatrix());
  182. }
  183. }
  184. bone.getInvertedAbsoluteTransform().multiplyToArray(bone.getWorldMatrix(), targetMatrix, index * 16);
  185. }
  186. this._identity.copyToArray(targetMatrix, this.bones.length * 16);
  187. }
  188. public prepare(): void {
  189. if (!this._isDirty) {
  190. return;
  191. }
  192. if (this.needInitialSkinMatrix) {
  193. for (var index = 0; index < this._meshesWithPoseMatrix.length; index++) {
  194. var mesh = this._meshesWithPoseMatrix[index];
  195. if (!mesh._bonesTransformMatrices || mesh._bonesTransformMatrices.length !== 16 * (this.bones.length + 1)) {
  196. mesh._bonesTransformMatrices = new Float32Array(16 * (this.bones.length + 1));
  197. }
  198. var poseMatrix = mesh.getPoseMatrix();
  199. // Prepare bones
  200. for (var boneIndex = 0; boneIndex < this.bones.length; boneIndex++) {
  201. var bone = this.bones[boneIndex];
  202. if (!bone.getParent()) {
  203. var matrix = bone.getBaseMatrix();
  204. matrix.multiplyToRef(poseMatrix, Tmp.Matrix[0]);
  205. bone._updateDifferenceMatrix(Tmp.Matrix[0]);
  206. }
  207. }
  208. this._computeTransformMatrices(mesh._bonesTransformMatrices, poseMatrix);
  209. }
  210. } else {
  211. if (!this._transformMatrices || this._transformMatrices.length !== 16 * (this.bones.length + 1)) {
  212. this._transformMatrices = new Float32Array(16 * (this.bones.length + 1));
  213. }
  214. this._computeTransformMatrices(this._transformMatrices, null);
  215. }
  216. this._isDirty = false;
  217. this._scene._activeBones += this.bones.length;
  218. }
  219. public getAnimatables(): IAnimatable[] {
  220. if (!this._animatables || this._animatables.length !== this.bones.length) {
  221. this._animatables = [];
  222. for (var index = 0; index < this.bones.length; index++) {
  223. this._animatables.push(this.bones[index]);
  224. }
  225. }
  226. return this._animatables;
  227. }
  228. public clone(name: string, id: string): Skeleton {
  229. var result = new Skeleton(name, id || name, this._scene);
  230. result.needInitialSkinMatrix = this.needInitialSkinMatrix;
  231. for (var index = 0; index < this.bones.length; index++) {
  232. var source = this.bones[index];
  233. var parentBone = null;
  234. if (source.getParent()) {
  235. var parentIndex = this.bones.indexOf(source.getParent());
  236. parentBone = result.bones[parentIndex];
  237. }
  238. var bone = new Bone(source.name, result, parentBone, source.getBaseMatrix().clone(), source.getRestPose().clone());
  239. Tools.DeepCopy(source.animations, bone.animations);
  240. }
  241. if (this._ranges) {
  242. result._ranges = {};
  243. for (var rangeName in this._ranges) {
  244. result._ranges[rangeName] = this._ranges[rangeName].clone();
  245. }
  246. }
  247. this._isDirty = true;
  248. return result;
  249. }
  250. public enableBlending(blendingSpeed = 0.01) {
  251. this.bones.forEach((bone) => {
  252. bone.animations.forEach((animation: Animation) => {
  253. animation.enableBlending = true;
  254. animation.blendingSpeed = blendingSpeed;
  255. });
  256. });
  257. }
  258. public dispose() {
  259. this._meshesWithPoseMatrix = [];
  260. // Animations
  261. this.getScene().stopAnimation(this);
  262. // Remove from scene
  263. this.getScene().removeSkeleton(this);
  264. }
  265. public serialize(): any {
  266. var serializationObject: any = {};
  267. serializationObject.name = this.name;
  268. serializationObject.id = this.id;
  269. serializationObject.dimensionsAtRest = this.dimensionsAtRest;
  270. serializationObject.bones = [];
  271. serializationObject.needInitialSkinMatrix = this.needInitialSkinMatrix;
  272. for (var index = 0; index < this.bones.length; index++) {
  273. var bone = this.bones[index];
  274. var serializedBone: any = {
  275. parentBoneIndex: bone.getParent() ? this.bones.indexOf(bone.getParent()) : -1,
  276. name: bone.name,
  277. matrix: bone.getLocalMatrix().toArray(),
  278. rest: bone.getRestPose().toArray()
  279. };
  280. serializationObject.bones.push(serializedBone);
  281. if (bone.length) {
  282. serializedBone.length = bone.length;
  283. }
  284. if (bone.animations && bone.animations.length > 0) {
  285. serializedBone.animation = bone.animations[0].serialize();
  286. }
  287. serializationObject.ranges = [];
  288. for (var name in this._ranges) {
  289. var range: any = {};
  290. range.name = name;
  291. range.from = this._ranges[name].from;
  292. range.to = this._ranges[name].to;
  293. serializationObject.ranges.push(range);
  294. }
  295. }
  296. return serializationObject;
  297. }
  298. public static Parse(parsedSkeleton: any, scene: Scene): Skeleton {
  299. var skeleton = new Skeleton(parsedSkeleton.name, parsedSkeleton.id, scene);
  300. if (parsedSkeleton.dimensionsAtRest) {
  301. skeleton.dimensionsAtRest = Vector3.FromArray(parsedSkeleton.dimensionsAtRest);
  302. }
  303. skeleton.needInitialSkinMatrix = parsedSkeleton.needInitialSkinMatrix;
  304. let index: number;
  305. for (index = 0; index < parsedSkeleton.bones.length; index++) {
  306. var parsedBone = parsedSkeleton.bones[index];
  307. var parentBone = null;
  308. if (parsedBone.parentBoneIndex > -1) {
  309. parentBone = skeleton.bones[parsedBone.parentBoneIndex];
  310. }
  311. var rest: Matrix = parsedBone.rest ? Matrix.FromArray(parsedBone.rest) : null;
  312. var bone = new Bone(parsedBone.name, skeleton, parentBone, Matrix.FromArray(parsedBone.matrix), rest);
  313. if (parsedBone.length) {
  314. bone.length = parsedBone.length;
  315. }
  316. if (parsedBone.animation) {
  317. bone.animations.push(Animation.Parse(parsedBone.animation));
  318. }
  319. }
  320. // placed after bones, so createAnimationRange can cascade down
  321. if (parsedSkeleton.ranges) {
  322. for (index = 0; index < parsedSkeleton.ranges.length; index++) {
  323. var data = parsedSkeleton.ranges[index];
  324. skeleton.createAnimationRange(data.name, data.from, data.to);
  325. }
  326. }
  327. return skeleton;
  328. }
  329. }
  330. }