skeletonViewer.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. import { Vector3, Matrix, TmpVectors } from "../Maths/math.vector";
  2. import { Color3 } from '../Maths/math.color';
  3. import { Scene } from "../scene";
  4. import { Nullable } from "../types";
  5. import { Bone } from "../Bones/bone";
  6. import { Skeleton } from "../Bones/skeleton";
  7. import { AbstractMesh } from "../Meshes/abstractMesh";
  8. import { Mesh } from "../Meshes/mesh";
  9. import { LinesMesh } from "../Meshes/linesMesh";
  10. import { LinesBuilder } from "../Meshes/Builders/linesBuilder";
  11. import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
  12. import { StandardMaterial } from '../Materials/standardMaterial';
  13. import { VertexBuffer } from '../Meshes/buffer';
  14. import { ISkeletonViewerOptions } from './ISkeletonViewer';
  15. import { Observer } from '../Misc/observable';
  16. import { SphereBuilder } from '../Meshes/Builders/sphereBuilder';
  17. import { ShapeBuilder } from '../Meshes/Builders/shapeBuilder';
  18. /**
  19. * Class used to render a debug view of a given skeleton
  20. * @see http://www.babylonjs-playground.com/#1BZJVJ#8
  21. */
  22. export class SkeletonViewer {
  23. /** public Display constants BABYLON.SkeletonViewer.DISPLAY_LINES */
  24. public static readonly DISPLAY_LINES = 0;
  25. /** public Display constants BABYLON.SkeletonViewer.DISPLAY_SPHERES */
  26. public static readonly DISPLAY_SPHERES = 1;
  27. /** public Display constants BABYLON.SkeletonViewer.DISPLAY_SPHERE_AND_SPURS */
  28. public static readonly DISPLAY_SPHERE_AND_SPURS = 2;
  29. /** If SkeletonViewer scene scope. */
  30. private _scene : Scene;
  31. /** Gets or sets the color used to render the skeleton */
  32. public color: Color3 = Color3.White();
  33. /** Array of the points of the skeleton fo the line view. */
  34. private _debugLines = new Array<Array<Vector3>>();
  35. /** The SkeletonViewers Mesh. */
  36. private _debugMesh: Nullable<LinesMesh>;
  37. /** If SkeletonViewer is enabled. */
  38. private _isEnabled = false;
  39. /** If SkeletonViewer is ready. */
  40. private _ready : boolean;
  41. /** SkeletonViewer render observable. */
  42. private _obs: Nullable<Observer<Scene>> = null;
  43. /** The Utility Layer to render the gizmos in. */
  44. private _utilityLayer: Nullable<UtilityLayerRenderer>;
  45. /** Gets the Scene. */
  46. get scene(): Scene {
  47. return this._scene;
  48. }
  49. /** Gets the utilityLayer. */
  50. get utilityLayer(): Nullable<UtilityLayerRenderer> {
  51. return this._utilityLayer;
  52. }
  53. /** Checks Ready Status. */
  54. get isReady(): Boolean {
  55. return this._ready;
  56. }
  57. /** Sets Ready Status. */
  58. set ready(value: boolean) {
  59. this._ready = value;
  60. }
  61. /** Gets the debugMesh */
  62. get debugMesh(): Nullable<AbstractMesh> | Nullable<LinesMesh> {
  63. return this._debugMesh;
  64. }
  65. /** Sets the debugMesh */
  66. set debugMesh(value: Nullable<AbstractMesh> | Nullable<LinesMesh>) {
  67. this._debugMesh = (value as any);
  68. }
  69. /** Gets the material */
  70. get material(): StandardMaterial {
  71. return this.material;
  72. }
  73. /** Sets the material */
  74. set material(value: StandardMaterial) {
  75. this.material = value;
  76. }
  77. /**
  78. * Creates a new SkeletonViewer
  79. * @param skeleton defines the skeleton to render
  80. * @param mesh defines the mesh attached to the skeleton
  81. * @param scene defines the hosting scene
  82. * @param autoUpdateBonesMatrices defines a boolean indicating if bones matrices must be forced to update before rendering (true by default)
  83. * @param renderingGroupId defines the rendering group id to use with the viewer
  84. * @param options All of the extra constructor options for the SkeletonViewer
  85. */
  86. constructor(
  87. /** defines the skeleton to render */
  88. public skeleton: Skeleton,
  89. /** defines the mesh attached to the skeleton */
  90. public mesh: AbstractMesh,
  91. /** The Scene scope*/
  92. scene: Scene,
  93. /** defines a boolean indicating if bones matrices must be forced to update before rendering (true by default) */
  94. public autoUpdateBonesMatrices: boolean = true,
  95. /** defines the rendering group id to use with the viewer */
  96. public renderingGroupId: number = 3,
  97. /** is the options for the viewer */
  98. public options: Partial<ISkeletonViewerOptions> = {}
  99. ) {
  100. this._scene = scene;
  101. this._ready = false;
  102. //Defaults
  103. options.pauseAnimations = options.pauseAnimations ?? true;
  104. options.returnToRest = options.returnToRest ?? true;
  105. options.displayMode = options.displayMode ?? SkeletonViewer.DISPLAY_LINES;
  106. options.displayOptions = options.displayOptions ?? {};
  107. options.displayOptions.midStep = options.displayOptions.midStep ?? 0.235;
  108. options.displayOptions.midStepFactor = options.displayOptions.midStepFactor ?? 0.155;
  109. options.displayOptions.sphereBaseSize = options.displayOptions.sphereBaseSize ?? 0.15;
  110. options.displayOptions.sphereScaleUnit = options.displayOptions.sphereScaleUnit ?? 2;
  111. options.displayOptions.sphereFactor = options.displayOptions.sphereFactor ?? 0.865;
  112. options.computeBonesUsingShaders = options.computeBonesUsingShaders ?? true;
  113. /* Create Utility Layer */
  114. this._utilityLayer = new UtilityLayerRenderer(this._scene, false);
  115. this._utilityLayer.pickUtilitySceneFirst = false;
  116. this._utilityLayer.utilityLayerScene.autoClearDepthAndStencil = true;
  117. let displayMode = this.options.displayMode || 0;
  118. if (displayMode > SkeletonViewer.DISPLAY_SPHERE_AND_SPURS) {
  119. displayMode = SkeletonViewer.DISPLAY_LINES;
  120. }
  121. this.options.displayMode = displayMode;
  122. //Prep the Systems
  123. this.update();
  124. this._bindObs();
  125. }
  126. /** The Dynamic bindings for the update functions */
  127. private _bindObs(): void {
  128. switch (this.options.displayMode){
  129. case SkeletonViewer.DISPLAY_LINES: {
  130. this._obs = this.scene.onBeforeRenderObservable.add(() => {
  131. this._displayLinesUpdate();
  132. });
  133. break;
  134. }
  135. }
  136. }
  137. /** Update the viewer to sync with current skeleton state, only used to manually update. */
  138. public update(): void {
  139. switch (this.options.displayMode){
  140. case SkeletonViewer.DISPLAY_LINES: {
  141. this._displayLinesUpdate();
  142. break;
  143. }
  144. case SkeletonViewer.DISPLAY_SPHERES: {
  145. this._buildSpheresAndSpurs(true);
  146. break;
  147. }
  148. case SkeletonViewer.DISPLAY_SPHERE_AND_SPURS: {
  149. this._buildSpheresAndSpurs(false);
  150. break;
  151. }
  152. }
  153. }
  154. /** Gets or sets a boolean indicating if the viewer is enabled */
  155. public set isEnabled(value: boolean) {
  156. if (this.isEnabled === value) {
  157. return;
  158. }
  159. this._isEnabled = value;
  160. if (this.debugMesh) {
  161. this.debugMesh.setEnabled(value);
  162. }
  163. if (value && !this._obs) {
  164. this._bindObs();
  165. } else if (!value && this._obs) {
  166. this.scene.onBeforeRenderObservable.remove(this._obs);
  167. this._obs = null;
  168. }
  169. }
  170. public get isEnabled(): boolean {
  171. return this._isEnabled;
  172. }
  173. private _getBonePosition(position: Vector3, bone: Bone, meshMat: Matrix, x = 0, y = 0, z = 0): void {
  174. var tmat = TmpVectors.Matrix[0];
  175. var parentBone = bone.getParent();
  176. tmat.copyFrom(bone.getLocalMatrix());
  177. if (x !== 0 || y !== 0 || z !== 0) {
  178. var tmat2 = TmpVectors.Matrix[1];
  179. Matrix.IdentityToRef(tmat2);
  180. tmat2.setTranslationFromFloats(x, y, z);
  181. tmat2.multiplyToRef(tmat, tmat);
  182. }
  183. if (parentBone) {
  184. tmat.multiplyToRef(parentBone.getAbsoluteTransform(), tmat);
  185. }
  186. tmat.multiplyToRef(meshMat, tmat);
  187. position.x = tmat.m[12];
  188. position.y = tmat.m[13];
  189. position.z = tmat.m[14];
  190. }
  191. private _getLinesForBonesWithLength(bones: Bone[], meshMat: Matrix): void {
  192. var len = bones.length;
  193. let mesh = this.mesh._effectiveMesh;
  194. var meshPos = mesh.position;
  195. for (var i = 0; i < len; i++) {
  196. var bone = bones[i];
  197. var points = this._debugLines[i];
  198. if (!points) {
  199. points = [Vector3.Zero(), Vector3.Zero()];
  200. this._debugLines[i] = points;
  201. }
  202. this._getBonePosition(points[0], bone, meshMat);
  203. this._getBonePosition(points[1], bone, meshMat, 0, bone.length, 0);
  204. points[0].subtractInPlace(meshPos);
  205. points[1].subtractInPlace(meshPos);
  206. }
  207. }
  208. private _getLinesForBonesNoLength(bones: Bone[]): void {
  209. var len = bones.length;
  210. var boneNum = 0;
  211. let mesh = this.mesh._effectiveMesh;
  212. var meshPos = mesh.position;
  213. for (var i = len - 1; i >= 0; i--) {
  214. var childBone = bones[i];
  215. var parentBone = childBone.getParent();
  216. if (!parentBone) {
  217. continue;
  218. }
  219. var points = this._debugLines[boneNum];
  220. if (!points) {
  221. points = [Vector3.Zero(), Vector3.Zero()];
  222. this._debugLines[boneNum] = points;
  223. }
  224. childBone.getAbsolutePositionToRef(mesh, points[0]);
  225. parentBone.getAbsolutePositionToRef(mesh, points[1]);
  226. points[0].subtractInPlace(meshPos);
  227. points[1].subtractInPlace(meshPos);
  228. boneNum++;
  229. }
  230. }
  231. /** function to revert the mesh and scene back to the initial state. */
  232. private _revert(): void {
  233. if (this.options.pauseAnimations) {
  234. this.scene.animationsEnabled = true;
  235. }
  236. }
  237. /** function to build and bind sphere joint points and spur bone representations. */
  238. private _buildSpheresAndSpurs(spheresOnly = true): Promise<void> {
  239. this.dispose();
  240. this._ready = false;
  241. let scene = this.scene;
  242. let bones: Bone[] = this.skeleton.bones;
  243. let spheres: Mesh[] = [];
  244. let spurs: Mesh[] = [];
  245. return new Promise((resolve, reject) => {
  246. try {
  247. if (this.options.pauseAnimations) {
  248. scene.animationsEnabled = false;
  249. }
  250. if (this.options.returnToRest) {
  251. this.skeleton.returnToRest();
  252. }
  253. if (this.autoUpdateBonesMatrices) {
  254. this.skeleton.computeAbsoluteTransforms();
  255. }
  256. let longestBoneLength = Number.NEGATIVE_INFINITY;
  257. let getAbsoluteRestPose = function(bone: Nullable<Bone>, matrix: Matrix) {
  258. if (bone == null) {
  259. matrix.copyFrom(Matrix.Identity());
  260. return;
  261. }
  262. getAbsoluteRestPose(bone.getParent(), matrix);
  263. bone.getRestPose().multiplyToRef(matrix, matrix);
  264. return;
  265. };
  266. let displayOptions = this.options.displayOptions || {};
  267. for (let i = 0; i < bones.length; i++) {
  268. let bone: Bone = bones[i];
  269. if (bone._index === null) {
  270. bone._index = i;
  271. }
  272. if (bone._index === -1) {
  273. continue;
  274. }
  275. let boneAbsoluteRestTransform = new Matrix();
  276. getAbsoluteRestPose(bone, boneAbsoluteRestTransform);
  277. let anchorPoint = new Vector3();
  278. boneAbsoluteRestTransform.decompose(undefined, undefined, anchorPoint);
  279. bone.children.forEach((bc, i) => {
  280. let childAbsoluteRestTransform : Matrix = new Matrix();
  281. bc.getRestPose().multiplyToRef(boneAbsoluteRestTransform, childAbsoluteRestTransform);
  282. let childPoint = new Vector3();
  283. childAbsoluteRestTransform.decompose(undefined, undefined, childPoint);
  284. let distanceFromParent = Vector3.Distance(anchorPoint, childPoint);
  285. if (distanceFromParent > longestBoneLength) {
  286. longestBoneLength = distanceFromParent;
  287. }
  288. if (spheresOnly) {
  289. return;
  290. }
  291. let dir = childPoint.clone().subtract(anchorPoint.clone());
  292. let h = dir.length();
  293. let up = dir.normalize().scale(h);
  294. let midStep = displayOptions.midStep || 0.165;
  295. let midStepFactor = displayOptions.midStepFactor || 0.215;
  296. let up0 = up.scale(midStep);
  297. let spur = ShapeBuilder.ExtrudeShapeCustom(bc.name + ':spur',
  298. {
  299. shape: [
  300. new Vector3(1, -1, 0),
  301. new Vector3(1, 1, 0),
  302. new Vector3(-1, 1, 0),
  303. new Vector3(-1, -1, 0),
  304. new Vector3(1, -1, 0)
  305. ],
  306. path: [ Vector3.Zero(), up0, up ],
  307. scaleFunction:
  308. (i: number) => {
  309. switch (i){
  310. case 0:
  311. case 2:
  312. return 0;
  313. case 1:
  314. return h * midStepFactor;
  315. }
  316. return 0;
  317. },
  318. sideOrientation: Mesh.DEFAULTSIDE,
  319. updatable: true
  320. }, scene);
  321. let ind = spur.getIndices() || [];
  322. let mwk: number[] = [], mik: number[] = [];
  323. for (let i = 0; i < ind.length; i++) {
  324. mwk.push(1, 0, 0, 0);
  325. mik.push(bone.getIndex(), 0, 0, 0);
  326. }
  327. spur.convertToFlatShadedMesh();
  328. spur.position = anchorPoint.clone();
  329. spur.setVerticesData(VertexBuffer.MatricesWeightsKind, mwk, false);
  330. spur.setVerticesData(VertexBuffer.MatricesIndicesKind, mik, false);
  331. spurs.push(spur);
  332. });
  333. let sphereBaseSize = displayOptions.sphereBaseSize || 0.2;
  334. let sphere = SphereBuilder.CreateSphere(bone.name + ':sphere', {
  335. segments: 6,
  336. diameter: sphereBaseSize,
  337. updatable: true
  338. }, scene);
  339. let ind = sphere.getIndices() || [];
  340. let mwk: number[] = [], mik: number[] = [];
  341. for (let i = 0; i < ind.length; i++) {
  342. mwk.push(1, 0, 0, 0);
  343. mik.push(bone.getIndex(), 0, 0, 0);
  344. }
  345. sphere.setVerticesData(VertexBuffer.MatricesWeightsKind, mwk, false);
  346. sphere.setVerticesData(VertexBuffer.MatricesIndicesKind, mik, false);
  347. sphere.position = anchorPoint.clone();
  348. spheres.push(sphere);
  349. }
  350. let skip = 0;
  351. let sphereScaleUnit = displayOptions.sphereScaleUnit || 2;
  352. let sphereFactor = displayOptions.sphereFactor || 0.85;
  353. for (let i = 0; i < bones.length; i++) {
  354. let bone: Nullable<Bone> = bones[i];
  355. if (bone.getIndex() === -1) {
  356. skip++;
  357. continue;
  358. }
  359. let sphere = spheres[i - skip];
  360. let scale = 1 / (sphereScaleUnit / longestBoneLength);
  361. let _stepsOut = 0;
  362. let _b: Bone = (bone as Bone) || {};
  363. while ((_b.getParent()) && (_b.getParent() as Bone).getIndex() !== -1) {
  364. _stepsOut++;
  365. _b = (_b.getParent() as Bone);
  366. }
  367. sphere.scaling.scaleInPlace(scale * Math.pow(sphereFactor, _stepsOut));
  368. }
  369. this.debugMesh = Mesh.MergeMeshes(spheres.concat(spurs), true, true);
  370. if (this.debugMesh) {
  371. this.debugMesh.renderingGroupId = this.renderingGroupId;
  372. this.debugMesh.skeleton = this.skeleton;
  373. this.debugMesh.parent = this.mesh;
  374. this.debugMesh.computeBonesUsingShaders = this.options.computeBonesUsingShaders ?? true;
  375. }
  376. resolve();
  377. } catch (err) {
  378. console.log(err);
  379. this._revert();
  380. this.dispose();
  381. }
  382. }).then(() => {
  383. this._revert();
  384. this.ready = true;
  385. }).catch((err) => {
  386. console.log(err);
  387. this.dispose();
  388. });
  389. }
  390. /** Update the viewer to sync with current skeleton state, only used for the line display. */
  391. private _displayLinesUpdate() {
  392. if (!this._utilityLayer) {
  393. return;
  394. }
  395. if (this.autoUpdateBonesMatrices) {
  396. this.skeleton.computeAbsoluteTransforms();
  397. }
  398. let mesh = this.mesh._effectiveMesh;
  399. if (this.skeleton.bones[0].length === undefined) {
  400. this._getLinesForBonesNoLength(this.skeleton.bones);
  401. } else {
  402. this._getLinesForBonesWithLength(this.skeleton.bones, mesh.getWorldMatrix());
  403. }
  404. const targetScene = this._utilityLayer.utilityLayerScene;
  405. if (targetScene) {
  406. if (!this._debugMesh) {
  407. this._debugMesh = LinesBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: null }, targetScene);
  408. this._debugMesh.renderingGroupId = this.renderingGroupId;
  409. } else {
  410. LinesBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: this._debugMesh }, targetScene);
  411. }
  412. this._debugMesh.position.copyFrom(this.mesh.position);
  413. this._debugMesh.color = this.color;
  414. }
  415. }
  416. /** Release associated resources */
  417. public dispose() {
  418. this.isEnabled = false;
  419. if (this._debugMesh) {
  420. this._debugMesh.dispose();
  421. this._debugMesh = null;
  422. }
  423. if (this._utilityLayer) {
  424. this._utilityLayer.dispose();
  425. this._utilityLayer = null;
  426. }
  427. }
  428. }