skeletonViewer.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  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. private _boneIndices: Set<number>;
  46. /** Gets the Scene. */
  47. get scene(): Scene {
  48. return this._scene;
  49. }
  50. /** Gets the utilityLayer. */
  51. get utilityLayer(): Nullable<UtilityLayerRenderer> {
  52. return this._utilityLayer;
  53. }
  54. /** Checks Ready Status. */
  55. get isReady(): Boolean {
  56. return this._ready;
  57. }
  58. /** Sets Ready Status. */
  59. set ready(value: boolean) {
  60. this._ready = value;
  61. }
  62. /** Gets the debugMesh */
  63. get debugMesh(): Nullable<AbstractMesh> | Nullable<LinesMesh> {
  64. return this._debugMesh;
  65. }
  66. /** Sets the debugMesh */
  67. set debugMesh(value: Nullable<AbstractMesh> | Nullable<LinesMesh>) {
  68. this._debugMesh = (value as any);
  69. }
  70. /** Gets the material */
  71. get material(): StandardMaterial {
  72. return this.material;
  73. }
  74. /** Sets the material */
  75. set material(value: StandardMaterial) {
  76. this.material = value;
  77. }
  78. /** Gets the material */
  79. get displayMode(): number {
  80. return this.options.displayMode || SkeletonViewer.DISPLAY_LINES;
  81. }
  82. /** Sets the material */
  83. set displayMode(value: number) {
  84. if (value > SkeletonViewer.DISPLAY_SPHERE_AND_SPURS) {
  85. value = SkeletonViewer.DISPLAY_LINES;
  86. }
  87. this.options.displayMode = value;
  88. }
  89. /**
  90. * Creates a new SkeletonViewer
  91. * @param skeleton defines the skeleton to render
  92. * @param mesh defines the mesh attached to the skeleton
  93. * @param scene defines the hosting scene
  94. * @param autoUpdateBonesMatrices defines a boolean indicating if bones matrices must be forced to update before rendering (true by default)
  95. * @param renderingGroupId defines the rendering group id to use with the viewer
  96. * @param options All of the extra constructor options for the SkeletonViewer
  97. */
  98. constructor(
  99. /** defines the skeleton to render */
  100. public skeleton: Skeleton,
  101. /** defines the mesh attached to the skeleton */
  102. public mesh: AbstractMesh,
  103. /** The Scene scope*/
  104. scene: Scene,
  105. /** defines a boolean indicating if bones matrices must be forced to update before rendering (true by default) */
  106. public autoUpdateBonesMatrices: boolean = true,
  107. /** defines the rendering group id to use with the viewer */
  108. public renderingGroupId: number = 3,
  109. /** is the options for the viewer */
  110. public options: Partial<ISkeletonViewerOptions> = {}
  111. ) {
  112. this._scene = scene;
  113. this._ready = false;
  114. //Defaults
  115. options.pauseAnimations = options.pauseAnimations ?? true;
  116. options.returnToRest = options.returnToRest ?? true;
  117. options.displayMode = options.displayMode ?? SkeletonViewer.DISPLAY_LINES;
  118. options.displayOptions = options.displayOptions ?? {};
  119. options.displayOptions.midStep = options.displayOptions.midStep ?? 0.235;
  120. options.displayOptions.midStepFactor = options.displayOptions.midStepFactor ?? 0.155;
  121. options.displayOptions.sphereBaseSize = options.displayOptions.sphereBaseSize ?? 0.15;
  122. options.displayOptions.sphereScaleUnit = options.displayOptions.sphereScaleUnit ?? 2;
  123. options.displayOptions.sphereFactor = options.displayOptions.sphereFactor ?? 0.865;
  124. options.computeBonesUsingShaders = options.computeBonesUsingShaders ?? true;
  125. const boneIndices = mesh.getVerticesData(VertexBuffer.MatricesIndicesKind);
  126. const boneWeights = mesh.getVerticesData(VertexBuffer.MatricesWeightsKind);
  127. this._boneIndices = new Set();
  128. if (boneIndices && boneWeights) {
  129. for (let i = 0; i < boneIndices.length; ++i) {
  130. const index = boneIndices[i], weight = boneWeights[i];
  131. if (weight !== 0) {
  132. this._boneIndices.add(index);
  133. }
  134. }
  135. }
  136. /* Create Utility Layer */
  137. this._utilityLayer = new UtilityLayerRenderer(this._scene, false);
  138. this._utilityLayer.pickUtilitySceneFirst = false;
  139. this._utilityLayer.utilityLayerScene.autoClearDepthAndStencil = true;
  140. let displayMode = this.options.displayMode || 0;
  141. if (displayMode > SkeletonViewer.DISPLAY_SPHERE_AND_SPURS) {
  142. displayMode = SkeletonViewer.DISPLAY_LINES;
  143. }
  144. this.displayMode = displayMode;
  145. //Prep the Systems
  146. this.update();
  147. this._bindObs();
  148. }
  149. /** The Dynamic bindings for the update functions */
  150. private _bindObs(): void {
  151. switch (this.displayMode){
  152. case SkeletonViewer.DISPLAY_LINES: {
  153. this._obs = this.scene.onBeforeRenderObservable.add(() => {
  154. this._displayLinesUpdate();
  155. });
  156. break;
  157. }
  158. }
  159. }
  160. /** Update the viewer to sync with current skeleton state, only used to manually update. */
  161. public update(): void {
  162. switch (this.displayMode){
  163. case SkeletonViewer.DISPLAY_LINES: {
  164. this._displayLinesUpdate();
  165. break;
  166. }
  167. case SkeletonViewer.DISPLAY_SPHERES: {
  168. this._buildSpheresAndSpurs(true);
  169. break;
  170. }
  171. case SkeletonViewer.DISPLAY_SPHERE_AND_SPURS: {
  172. this._buildSpheresAndSpurs(false);
  173. break;
  174. }
  175. }
  176. }
  177. /** Gets or sets a boolean indicating if the viewer is enabled */
  178. public set isEnabled(value: boolean) {
  179. if (this.isEnabled === value) {
  180. return;
  181. }
  182. this._isEnabled = value;
  183. if (this.debugMesh) {
  184. this.debugMesh.setEnabled(value);
  185. }
  186. if (value && !this._obs) {
  187. this._bindObs();
  188. } else if (!value && this._obs) {
  189. this.scene.onBeforeRenderObservable.remove(this._obs);
  190. this._obs = null;
  191. }
  192. }
  193. public get isEnabled(): boolean {
  194. return this._isEnabled;
  195. }
  196. private _getBonePosition(position: Vector3, bone: Bone, meshMat: Matrix, x = 0, y = 0, z = 0): void {
  197. var tmat = TmpVectors.Matrix[0];
  198. var parentBone = bone.getParent();
  199. tmat.copyFrom(bone.getLocalMatrix());
  200. if (x !== 0 || y !== 0 || z !== 0) {
  201. var tmat2 = TmpVectors.Matrix[1];
  202. Matrix.IdentityToRef(tmat2);
  203. tmat2.setTranslationFromFloats(x, y, z);
  204. tmat2.multiplyToRef(tmat, tmat);
  205. }
  206. if (parentBone) {
  207. tmat.multiplyToRef(parentBone.getAbsoluteTransform(), tmat);
  208. }
  209. tmat.multiplyToRef(meshMat, tmat);
  210. position.x = tmat.m[12];
  211. position.y = tmat.m[13];
  212. position.z = tmat.m[14];
  213. }
  214. private _getLinesForBonesWithLength(bones: Bone[], meshMat: Matrix): void {
  215. var len = bones.length;
  216. let mesh = this.mesh._effectiveMesh;
  217. var meshPos = mesh.position;
  218. let idx = 0;
  219. for (var i = 0; i < len; i++) {
  220. var bone = bones[i];
  221. var points = this._debugLines[idx];
  222. if (bone._index === -1 || !this._boneIndices.has(bone.getIndex())) {
  223. continue;
  224. }
  225. if (!points) {
  226. points = [Vector3.Zero(), Vector3.Zero()];
  227. this._debugLines[idx] = points;
  228. }
  229. this._getBonePosition(points[0], bone, meshMat);
  230. this._getBonePosition(points[1], bone, meshMat, 0, bone.length, 0);
  231. points[0].subtractInPlace(meshPos);
  232. points[1].subtractInPlace(meshPos);
  233. idx++;
  234. }
  235. }
  236. private _getLinesForBonesNoLength(bones: Bone[]): void {
  237. var len = bones.length;
  238. var boneNum = 0;
  239. let mesh = this.mesh._effectiveMesh;
  240. var meshPos = mesh.position;
  241. for (var i = len - 1; i >= 0; i--) {
  242. var childBone = bones[i];
  243. var parentBone = childBone.getParent();
  244. if (!parentBone || !this._boneIndices.has(childBone.getIndex())) {
  245. continue;
  246. }
  247. var points = this._debugLines[boneNum];
  248. if (!points) {
  249. points = [Vector3.Zero(), Vector3.Zero()];
  250. this._debugLines[boneNum] = points;
  251. }
  252. childBone.getAbsolutePositionToRef(mesh, points[0]);
  253. parentBone.getAbsolutePositionToRef(mesh, points[1]);
  254. points[0].subtractInPlace(meshPos);
  255. points[1].subtractInPlace(meshPos);
  256. boneNum++;
  257. }
  258. }
  259. /** function to revert the mesh and scene back to the initial state. */
  260. private _revert(animationState: boolean): void {
  261. if (this.options.pauseAnimations) {
  262. this.scene.animationsEnabled = animationState;
  263. }
  264. }
  265. /** function to build and bind sphere joint points and spur bone representations. */
  266. private _buildSpheresAndSpurs(spheresOnly = true): void {
  267. if (this._debugMesh) {
  268. this._debugMesh.dispose();
  269. this._debugMesh = null;
  270. this.ready = false;
  271. }
  272. this._ready = false;
  273. let scene = this.scene;
  274. let bones: Bone[] = this.skeleton.bones;
  275. let spheres: Array<[Mesh, Bone]> = [];
  276. let spurs: Mesh[] = [];
  277. const animationState = scene.animationsEnabled;
  278. try {
  279. if (this.options.pauseAnimations) {
  280. scene.animationsEnabled = false;
  281. }
  282. if (this.options.returnToRest) {
  283. this.skeleton.returnToRest();
  284. }
  285. if (this.autoUpdateBonesMatrices) {
  286. this.skeleton.computeAbsoluteTransforms();
  287. }
  288. let longestBoneLength = Number.NEGATIVE_INFINITY;
  289. let getAbsoluteRestPose = function(bone: Nullable<Bone>, matrix: Matrix) {
  290. if (bone === null || bone._index === -1) {
  291. matrix.copyFrom(Matrix.Identity());
  292. return;
  293. }
  294. getAbsoluteRestPose(bone.getParent(), matrix);
  295. bone.getBindPose().multiplyToRef(matrix, matrix);
  296. return;
  297. };
  298. let displayOptions = this.options.displayOptions || {};
  299. for (let i = 0; i < bones.length; i++) {
  300. let bone = bones[i];
  301. if (bone._index === -1 || !this._boneIndices.has(bone.getIndex())) {
  302. continue;
  303. }
  304. let boneAbsoluteRestTransform = new Matrix();
  305. getAbsoluteRestPose(bone, boneAbsoluteRestTransform);
  306. let anchorPoint = new Vector3();
  307. boneAbsoluteRestTransform.decompose(undefined, undefined, anchorPoint);
  308. bone.children.forEach((bc, i) => {
  309. let childAbsoluteRestTransform : Matrix = new Matrix();
  310. bc.getRestPose().multiplyToRef(boneAbsoluteRestTransform, childAbsoluteRestTransform);
  311. let childPoint = new Vector3();
  312. childAbsoluteRestTransform.decompose(undefined, undefined, childPoint);
  313. let distanceFromParent = Vector3.Distance(anchorPoint, childPoint);
  314. if (distanceFromParent > longestBoneLength) {
  315. longestBoneLength = distanceFromParent;
  316. }
  317. if (spheresOnly) {
  318. return;
  319. }
  320. let dir = childPoint.clone().subtract(anchorPoint.clone());
  321. let h = dir.length();
  322. let up = dir.normalize().scale(h);
  323. let midStep = displayOptions.midStep || 0.165;
  324. let midStepFactor = displayOptions.midStepFactor || 0.215;
  325. let up0 = up.scale(midStep);
  326. let spur = ShapeBuilder.ExtrudeShapeCustom(bc.name + ':spur',
  327. {
  328. shape: [
  329. new Vector3(1, -1, 0),
  330. new Vector3(1, 1, 0),
  331. new Vector3(-1, 1, 0),
  332. new Vector3(-1, -1, 0),
  333. new Vector3(1, -1, 0)
  334. ],
  335. path: [ Vector3.Zero(), up0, up ],
  336. scaleFunction:
  337. (i: number) => {
  338. switch (i){
  339. case 0:
  340. case 2:
  341. return 0;
  342. case 1:
  343. return h * midStepFactor;
  344. }
  345. return 0;
  346. },
  347. sideOrientation: Mesh.DEFAULTSIDE,
  348. updatable: false
  349. }, scene);
  350. spur.convertToFlatShadedMesh();
  351. let numVertices = spur.getTotalVertices();
  352. let mwk: number[] = [], mik: number[] = [];
  353. for (let i = 0; i < numVertices; i++) {
  354. mwk.push(1, 0, 0, 0);
  355. mik.push(bone.getIndex(), 0, 0, 0);
  356. }
  357. spur.position = anchorPoint.clone();
  358. spur.setVerticesData(VertexBuffer.MatricesWeightsKind, mwk, false);
  359. spur.setVerticesData(VertexBuffer.MatricesIndicesKind, mik, false);
  360. spurs.push(spur);
  361. });
  362. let sphereBaseSize = displayOptions.sphereBaseSize || 0.2;
  363. let sphere = SphereBuilder.CreateSphere(bone.name + ':sphere', {
  364. segments: 6,
  365. diameter: sphereBaseSize,
  366. updatable: false
  367. }, scene);
  368. const numVertices = sphere.getTotalVertices();
  369. let mwk: number[] = [], mik: number[] = [];
  370. for (let i = 0; i < numVertices; i++) {
  371. mwk.push(1, 0, 0, 0);
  372. mik.push(bone.getIndex(), 0, 0, 0);
  373. }
  374. sphere.setVerticesData(VertexBuffer.MatricesWeightsKind, mwk, false);
  375. sphere.setVerticesData(VertexBuffer.MatricesIndicesKind, mik, false);
  376. sphere.position = anchorPoint.clone();
  377. spheres.push([sphere, bone]);
  378. }
  379. let sphereScaleUnit = displayOptions.sphereScaleUnit || 2;
  380. let sphereFactor = displayOptions.sphereFactor || 0.85;
  381. const meshes = [];
  382. for (let i = 0; i < spheres.length; i++) {
  383. let [sphere, bone] = spheres[i];
  384. let scale = 1 / (sphereScaleUnit / longestBoneLength);
  385. let _stepsOut = 0;
  386. let _b = bone;
  387. while ((_b.getParent()) && (_b.getParent() as Bone).getIndex() !== -1) {
  388. _stepsOut++;
  389. _b = (_b.getParent() as Bone);
  390. }
  391. sphere.scaling.scaleInPlace(scale * Math.pow(sphereFactor, _stepsOut));
  392. meshes.push(sphere);
  393. }
  394. this.debugMesh = Mesh.MergeMeshes(meshes.concat(spurs), true, true);
  395. if (this.debugMesh) {
  396. this.debugMesh.renderingGroupId = this.renderingGroupId;
  397. this.debugMesh.skeleton = this.skeleton;
  398. this.debugMesh.parent = this.mesh;
  399. this.debugMesh.computeBonesUsingShaders = this.options.computeBonesUsingShaders ?? true;
  400. this.debugMesh.alwaysSelectAsActiveMesh = true;
  401. }
  402. this._revert(animationState);
  403. this.ready = true;
  404. } catch (err) {
  405. console.error(err);
  406. this._revert(animationState);
  407. this.dispose();
  408. }
  409. }
  410. /** Update the viewer to sync with current skeleton state, only used for the line display. */
  411. private _displayLinesUpdate(): void {
  412. if (!this._utilityLayer) {
  413. return;
  414. }
  415. if (this.autoUpdateBonesMatrices) {
  416. this.skeleton.computeAbsoluteTransforms();
  417. }
  418. let mesh = this.mesh._effectiveMesh;
  419. if (this.skeleton.bones[0].length === undefined) {
  420. this._getLinesForBonesNoLength(this.skeleton.bones);
  421. } else {
  422. this._getLinesForBonesWithLength(this.skeleton.bones, mesh.getWorldMatrix());
  423. }
  424. const targetScene = this._utilityLayer.utilityLayerScene;
  425. if (targetScene) {
  426. if (!this._debugMesh) {
  427. this._debugMesh = LinesBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: null }, targetScene);
  428. this._debugMesh.renderingGroupId = this.renderingGroupId;
  429. } else {
  430. LinesBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: this._debugMesh }, targetScene);
  431. }
  432. this._debugMesh.position.copyFrom(this.mesh.position);
  433. this._debugMesh.color = this.color;
  434. }
  435. }
  436. /** Changes the displayMode of the skeleton viewer
  437. * @param mode The displayMode numerical value
  438. */
  439. public changeDisplayMode(mode: number): void {
  440. let wasEnabled = (this.isEnabled) ? true : false;
  441. if (this.displayMode !== mode) {
  442. this.isEnabled = false;
  443. if (this._debugMesh) {
  444. this._debugMesh.dispose();
  445. this._debugMesh = null;
  446. this.ready = false;
  447. }
  448. this.displayMode = mode;
  449. this.update();
  450. this._bindObs();
  451. this.isEnabled = wasEnabled;
  452. }
  453. }
  454. /** Changes the displayMode of the skeleton viewer
  455. * @param option String of the option name
  456. * @param value The numerical option value
  457. */
  458. public changeDisplayOptions(option: string, value: number): void {
  459. let wasEnabled = (this.isEnabled) ? true : false;
  460. (this.options.displayOptions as any)[option] = value;
  461. this.isEnabled = false;
  462. if (this._debugMesh) {
  463. this._debugMesh.dispose();
  464. this._debugMesh = null;
  465. this.ready = false;
  466. }
  467. this.update();
  468. this._bindObs();
  469. this.isEnabled = wasEnabled;
  470. }
  471. /** Release associated resources */
  472. public dispose(): void {
  473. this.isEnabled = false;
  474. if (this._debugMesh) {
  475. this._debugMesh.dispose();
  476. this._debugMesh = null;
  477. }
  478. if (this._utilityLayer) {
  479. this._utilityLayer.dispose();
  480. this._utilityLayer = null;
  481. }
  482. this.ready = false;
  483. }
  484. }