skeletonViewer.ts 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934
  1. import { Vector3, Matrix, TmpVectors } from "../Maths/math.vector";
  2. import { Color3, Color4 } 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 { Material } from '../Materials/material';
  13. import { ShaderMaterial } from '../Materials/shaderMaterial';
  14. import { DynamicTexture } from '../Materials/Textures/dynamicTexture';
  15. import { VertexBuffer } from '../Meshes/buffer';
  16. import { Effect } from '../Materials/effect';
  17. import { ISkeletonViewerOptions, IBoneWeightShaderOptions, ISkeletonMapShaderOptions, ISkeletonMapShaderColorMapKnot } from './ISkeletonViewer';
  18. import { Observer } from '../Misc/observable';
  19. import { SphereBuilder } from '../Meshes/Builders/sphereBuilder';
  20. import { ShapeBuilder } from '../Meshes/Builders/shapeBuilder';
  21. /**
  22. * Class used to render a debug view of a given skeleton
  23. * @see http://www.babylonjs-playground.com/#1BZJVJ#8
  24. */
  25. export class SkeletonViewer {
  26. /** public Display constants BABYLON.SkeletonViewer.DISPLAY_LINES */
  27. public static readonly DISPLAY_LINES = 0;
  28. /** public Display constants BABYLON.SkeletonViewer.DISPLAY_SPHERES */
  29. public static readonly DISPLAY_SPHERES = 1;
  30. /** public Display constants BABYLON.SkeletonViewer.DISPLAY_SPHERE_AND_SPURS */
  31. public static readonly DISPLAY_SPHERE_AND_SPURS = 2;
  32. /** public static method to create a BoneWeight Shader
  33. * @param options The constructor options
  34. * @param scene The scene that the shader is scoped to
  35. * @returns The created ShaderMaterial
  36. * @see http://www.babylonjs-playground.com/#1BZJVJ#395
  37. */
  38. static CreateBoneWeightShader(options: IBoneWeightShaderOptions, scene: Scene): ShaderMaterial {
  39. let skeleton: Skeleton = options.skeleton;
  40. let colorBase: Color3 = options.colorBase ?? Color3.Black();
  41. let colorZero: Color3 = options.colorZero ?? Color3.Blue();
  42. let colorQuarter: Color3 = options.colorQuarter ?? Color3.Green();
  43. let colorHalf: Color3 = options.colorHalf ?? Color3.Yellow();
  44. let colorFull: Color3 = options.colorFull ?? Color3.Red();
  45. let targetBoneIndex: number = options.targetBoneIndex ?? 0;
  46. Effect.ShadersStore['boneWeights:' + skeleton.name + "VertexShader"] =
  47. `precision highp float;
  48. attribute vec3 position;
  49. attribute vec2 uv;
  50. uniform mat4 view;
  51. uniform mat4 projection;
  52. uniform mat4 worldViewProjection;
  53. #include<bonesDeclaration>
  54. #if NUM_BONE_INFLUENCERS == 0
  55. attribute vec4 matricesIndices;
  56. attribute vec4 matricesWeights;
  57. #endif
  58. #include<instancesDeclaration>
  59. varying vec3 vColor;
  60. uniform vec3 colorBase;
  61. uniform vec3 colorZero;
  62. uniform vec3 colorQuarter;
  63. uniform vec3 colorHalf;
  64. uniform vec3 colorFull;
  65. uniform float targetBoneIndex;
  66. void main() {
  67. vec3 positionUpdated = position;
  68. #include<instancesVertex>
  69. #include<bonesVertex>
  70. vec4 worldPos = finalWorld * vec4(positionUpdated, 1.0);
  71. vec3 color = colorBase;
  72. float totalWeight = 0.;
  73. if(matricesIndices[0] == targetBoneIndex && matricesWeights[0] > 0.){
  74. totalWeight += matricesWeights[0];
  75. }
  76. if(matricesIndices[1] == targetBoneIndex && matricesWeights[1] > 0.){
  77. totalWeight += matricesWeights[1];
  78. }
  79. if(matricesIndices[2] == targetBoneIndex && matricesWeights[2] > 0.){
  80. totalWeight += matricesWeights[2];
  81. }
  82. if(matricesIndices[3] == targetBoneIndex && matricesWeights[3] > 0.){
  83. totalWeight += matricesWeights[3];
  84. }
  85. color = mix(color, colorZero, smoothstep(0., 0.25, totalWeight));
  86. color = mix(color, colorQuarter, smoothstep(0.25, 0.5, totalWeight));
  87. color = mix(color, colorHalf, smoothstep(0.5, 0.75, totalWeight));
  88. color = mix(color, colorFull, smoothstep(0.75, 1.0, totalWeight));
  89. vColor = color;
  90. gl_Position = projection * view * worldPos;
  91. }`;
  92. Effect.ShadersStore['boneWeights:' + skeleton.name + "FragmentShader"] =
  93. `
  94. precision highp float;
  95. varying vec3 vPosition;
  96. varying vec3 vColor;
  97. void main() {
  98. vec4 color = vec4(vColor, 1.0);
  99. gl_FragColor = color;
  100. }
  101. `;
  102. let shader: ShaderMaterial = new ShaderMaterial('boneWeight:' + skeleton.name, scene,
  103. {
  104. vertex: 'boneWeights:' + skeleton.name,
  105. fragment: 'boneWeights:' + skeleton.name
  106. },
  107. {
  108. attributes: ['position', 'normal', 'matricesIndices', 'matricesWeights'],
  109. uniforms: [
  110. 'world', 'worldView', 'worldViewProjection', 'view', 'projection', 'viewProjection',
  111. 'colorBase', 'colorZero', 'colorQuarter', 'colorHalf', 'colorFull', 'targetBoneIndex'
  112. ]
  113. });
  114. shader.setColor3('colorBase', colorBase);
  115. shader.setColor3('colorZero', colorZero);
  116. shader.setColor3('colorQuarter', colorQuarter);
  117. shader.setColor3('colorHalf', colorHalf);
  118. shader.setColor3('colorFull', colorFull);
  119. shader.setFloat('targetBoneIndex', targetBoneIndex);
  120. shader.getClassName = (): string => {
  121. return "BoneWeightShader";
  122. };
  123. shader.transparencyMode = Material.MATERIAL_OPAQUE;
  124. return shader;
  125. }
  126. /** public static method to create a BoneWeight Shader
  127. * @param options The constructor options
  128. * @param scene The scene that the shader is scoped to
  129. * @returns The created ShaderMaterial
  130. */
  131. static CreateSkeletonMapShader(options: ISkeletonMapShaderOptions, scene: Scene) {
  132. let skeleton: Skeleton = options.skeleton;
  133. let colorMap: ISkeletonMapShaderColorMapKnot[] = options.colorMap ?? [
  134. {
  135. color: new Color3(1, 0.38, 0.18),
  136. location : 0
  137. },
  138. {
  139. color: new Color3(.59, 0.18, 1.00),
  140. location : 0.2
  141. },
  142. {
  143. color: new Color3(0.59, 1, 0.18),
  144. location : 0.4
  145. },
  146. {
  147. color: new Color3(1, 0.87, 0.17),
  148. location : 0.6
  149. },
  150. {
  151. color: new Color3(1, 0.17, 0.42),
  152. location : 0.8
  153. },
  154. {
  155. color: new Color3(0.17, 0.68, 1.0),
  156. location : 1.0
  157. }
  158. ];
  159. let bufferWidth: number = skeleton.bones.length + 1;
  160. let colorMapBuffer: number[] = SkeletonViewer._CreateBoneMapColorBuffer(bufferWidth, colorMap, scene);
  161. let shader = new ShaderMaterial('boneWeights:' + skeleton.name, scene,
  162. {
  163. vertexSource:
  164. `precision highp float;
  165. attribute vec3 position;
  166. attribute vec2 uv;
  167. uniform mat4 view;
  168. uniform mat4 projection;
  169. uniform mat4 worldViewProjection;
  170. uniform float colorMap[` + ((skeleton.bones.length) * 4) + `];
  171. #include<bonesDeclaration>
  172. #if NUM_BONE_INFLUENCERS == 0
  173. attribute vec4 matricesIndices;
  174. attribute vec4 matricesWeights;
  175. #endif
  176. #include<instancesDeclaration>
  177. varying vec3 vColor;
  178. void main() {
  179. vec3 positionUpdated = position;
  180. #include<instancesVertex>
  181. #include<bonesVertex>
  182. vec3 color = vec3(0.);
  183. bool first = true;
  184. for (int i = 0; i < 4; i++) {
  185. int boneIdx = int(matricesIndices[i]);
  186. float boneWgt = matricesWeights[i];
  187. vec3 c = vec3(colorMap[boneIdx * 4 + 0], colorMap[boneIdx * 4 + 1], colorMap[boneIdx * 4 + 2]);
  188. if (boneWgt > 0.) {
  189. if (first) {
  190. first = false;
  191. color = c;
  192. } else {
  193. color = mix(color, c, boneWgt);
  194. }
  195. }
  196. }
  197. vColor = color;
  198. vec4 worldPos = finalWorld * vec4(positionUpdated, 1.0);
  199. gl_Position = projection * view * worldPos;
  200. }`,
  201. fragmentSource:
  202. `
  203. precision highp float;
  204. varying vec3 vColor;
  205. void main() {
  206. vec4 color = vec4( vColor, 1.0 );
  207. gl_FragColor = color;
  208. }
  209. `
  210. },
  211. {
  212. attributes: ['position', 'normal', 'matricesIndices', 'matricesWeights'],
  213. uniforms: [
  214. 'world', 'worldView', 'worldViewProjection', 'view', 'projection', 'viewProjection',
  215. 'colorMap'
  216. ]
  217. });
  218. shader.setFloats('colorMap', colorMapBuffer);
  219. shader.getClassName = (): string => {
  220. return "SkeletonMapShader";
  221. };
  222. shader.transparencyMode = Material.MATERIAL_OPAQUE;
  223. return shader;
  224. }
  225. /** private static method to create a BoneWeight Shader
  226. * @param size The size of the buffer to create (usually the bone count)
  227. * @param colorMap The gradient data to generate
  228. * @param scene The scene that the shader is scoped to
  229. * @returns an Array of floats from the color gradient values
  230. */
  231. private static _CreateBoneMapColorBuffer(size: number, colorMap: ISkeletonMapShaderColorMapKnot[], scene: Scene) {
  232. let tempGrad = new DynamicTexture('temp', {width: size, height: 1}, scene, false);
  233. let ctx = tempGrad.getContext();
  234. let grad = ctx.createLinearGradient(0, 0, size, 0);
  235. colorMap.forEach((stop) => {
  236. grad.addColorStop(stop.location, stop.color.toHexString());
  237. });
  238. ctx.fillStyle = grad;
  239. ctx.fillRect(0, 0, size, 1);
  240. tempGrad.update();
  241. let buffer: number[] = [];
  242. let data: Uint8ClampedArray = ctx.getImageData(0, 0, size, 1).data;
  243. let rUnit = 1 / 255;
  244. for (let i = 0; i < data.length; i++) {
  245. buffer.push(data[i] * rUnit);
  246. }
  247. tempGrad.dispose();
  248. return buffer;
  249. }
  250. /** If SkeletonViewer scene scope. */
  251. private _scene : Scene;
  252. /** Gets or sets the color used to render the skeleton */
  253. public color: Color3 = Color3.White();
  254. /** Array of the points of the skeleton fo the line view. */
  255. private _debugLines = new Array<Array<Vector3>>();
  256. /** The SkeletonViewers Mesh. */
  257. private _debugMesh: Nullable<LinesMesh>;
  258. /** The local axes Meshes. */
  259. private _localAxes: Nullable<LinesMesh> = null;
  260. /** If SkeletonViewer is enabled. */
  261. private _isEnabled = false;
  262. /** If SkeletonViewer is ready. */
  263. private _ready : boolean;
  264. /** SkeletonViewer render observable. */
  265. private _obs: Nullable<Observer<Scene>> = null;
  266. /** The Utility Layer to render the gizmos in. */
  267. private _utilityLayer: Nullable<UtilityLayerRenderer>;
  268. private _boneIndices: Set<number>;
  269. /** Gets the Scene. */
  270. get scene(): Scene {
  271. return this._scene;
  272. }
  273. /** Gets the utilityLayer. */
  274. get utilityLayer(): Nullable<UtilityLayerRenderer> {
  275. return this._utilityLayer;
  276. }
  277. /** Checks Ready Status. */
  278. get isReady(): Boolean {
  279. return this._ready;
  280. }
  281. /** Sets Ready Status. */
  282. set ready(value: boolean) {
  283. this._ready = value;
  284. }
  285. /** Gets the debugMesh */
  286. get debugMesh(): Nullable<AbstractMesh> | Nullable<LinesMesh> {
  287. return this._debugMesh;
  288. }
  289. /** Sets the debugMesh */
  290. set debugMesh(value: Nullable<AbstractMesh> | Nullable<LinesMesh>) {
  291. this._debugMesh = (value as any);
  292. }
  293. /** Gets the displayMode */
  294. get displayMode(): number {
  295. return this.options.displayMode || SkeletonViewer.DISPLAY_LINES;
  296. }
  297. /** Sets the displayMode */
  298. set displayMode(value: number) {
  299. if (value > SkeletonViewer.DISPLAY_SPHERE_AND_SPURS) {
  300. value = SkeletonViewer.DISPLAY_LINES;
  301. }
  302. this.options.displayMode = value;
  303. }
  304. /**
  305. * Creates a new SkeletonViewer
  306. * @param skeleton defines the skeleton to render
  307. * @param mesh defines the mesh attached to the skeleton
  308. * @param scene defines the hosting scene
  309. * @param autoUpdateBonesMatrices defines a boolean indicating if bones matrices must be forced to update before rendering (true by default)
  310. * @param renderingGroupId defines the rendering group id to use with the viewer
  311. * @param options All of the extra constructor options for the SkeletonViewer
  312. */
  313. constructor(
  314. /** defines the skeleton to render */
  315. public skeleton: Skeleton,
  316. /** defines the mesh attached to the skeleton */
  317. public mesh: AbstractMesh,
  318. /** The Scene scope*/
  319. scene: Scene,
  320. /** defines a boolean indicating if bones matrices must be forced to update before rendering (true by default) */
  321. public autoUpdateBonesMatrices: boolean = true,
  322. /** defines the rendering group id to use with the viewer */
  323. public renderingGroupId: number = 3,
  324. /** is the options for the viewer */
  325. public options: Partial<ISkeletonViewerOptions> = {}
  326. ) {
  327. this._scene = scene;
  328. this._ready = false;
  329. //Defaults
  330. options.pauseAnimations = options.pauseAnimations ?? true;
  331. options.returnToRest = options.returnToRest ?? false;
  332. options.displayMode = options.displayMode ?? SkeletonViewer.DISPLAY_LINES;
  333. options.displayOptions = options.displayOptions ?? {};
  334. options.displayOptions.midStep = options.displayOptions.midStep ?? 0.235;
  335. options.displayOptions.midStepFactor = options.displayOptions.midStepFactor ?? 0.155;
  336. options.displayOptions.sphereBaseSize = options.displayOptions.sphereBaseSize ?? 0.15;
  337. options.displayOptions.sphereScaleUnit = options.displayOptions.sphereScaleUnit ?? 2;
  338. options.displayOptions.sphereFactor = options.displayOptions.sphereFactor ?? 0.865;
  339. options.displayOptions.spurFollowsChild = options.displayOptions.spurFollowsChild ?? false;
  340. options.displayOptions.showLocalAxes = options.displayOptions.showLocalAxes ?? false;
  341. options.displayOptions.localAxesSize = options.displayOptions.localAxesSize ?? 0.075;
  342. options.computeBonesUsingShaders = options.computeBonesUsingShaders ?? true;
  343. options.useAllBones = options.useAllBones ?? true;
  344. const initialMeshBoneIndices = mesh.getVerticesData(VertexBuffer.MatricesIndicesKind);
  345. const initialMeshBoneWeights = mesh.getVerticesData(VertexBuffer.MatricesWeightsKind);
  346. this._boneIndices = new Set();
  347. if (!options.useAllBones) {
  348. if (initialMeshBoneIndices && initialMeshBoneWeights) {
  349. for (let i = 0; i < initialMeshBoneIndices.length; ++i) {
  350. const index = initialMeshBoneIndices[i], weight = initialMeshBoneWeights[i];
  351. if (weight !== 0) {
  352. this._boneIndices.add(index);
  353. }
  354. }
  355. }
  356. }
  357. /* Create Utility Layer */
  358. this._utilityLayer = new UtilityLayerRenderer(this._scene, false);
  359. this._utilityLayer.pickUtilitySceneFirst = false;
  360. this._utilityLayer.utilityLayerScene.autoClearDepthAndStencil = true;
  361. let displayMode = this.options.displayMode || 0;
  362. if (displayMode > SkeletonViewer.DISPLAY_SPHERE_AND_SPURS) {
  363. displayMode = SkeletonViewer.DISPLAY_LINES;
  364. }
  365. this.displayMode = displayMode;
  366. //Prep the Systems
  367. this.update();
  368. this._bindObs();
  369. }
  370. /** The Dynamic bindings for the update functions */
  371. private _bindObs(): void {
  372. switch (this.displayMode){
  373. case SkeletonViewer.DISPLAY_LINES: {
  374. this._obs = this.scene.onBeforeRenderObservable.add(() => {
  375. this._displayLinesUpdate();
  376. });
  377. break;
  378. }
  379. }
  380. }
  381. /** Update the viewer to sync with current skeleton state, only used to manually update. */
  382. public update(): void {
  383. switch (this.displayMode){
  384. case SkeletonViewer.DISPLAY_LINES: {
  385. this._displayLinesUpdate();
  386. break;
  387. }
  388. case SkeletonViewer.DISPLAY_SPHERES: {
  389. this._buildSpheresAndSpurs(true);
  390. break;
  391. }
  392. case SkeletonViewer.DISPLAY_SPHERE_AND_SPURS: {
  393. this._buildSpheresAndSpurs(false);
  394. break;
  395. }
  396. }
  397. this._buildLocalAxes();
  398. }
  399. /** Gets or sets a boolean indicating if the viewer is enabled */
  400. public set isEnabled(value: boolean) {
  401. if (this.isEnabled === value) {
  402. return;
  403. }
  404. this._isEnabled = value;
  405. if (this.debugMesh) {
  406. this.debugMesh.setEnabled(value);
  407. }
  408. if (value && !this._obs) {
  409. this._bindObs();
  410. } else if (!value && this._obs) {
  411. this.scene.onBeforeRenderObservable.remove(this._obs);
  412. this._obs = null;
  413. }
  414. }
  415. public get isEnabled(): boolean {
  416. return this._isEnabled;
  417. }
  418. private _getBonePosition(position: Vector3, bone: Bone, meshMat: Matrix, x = 0, y = 0, z = 0): void {
  419. var tmat = TmpVectors.Matrix[0];
  420. var parentBone = bone.getParent();
  421. tmat.copyFrom(bone.getLocalMatrix());
  422. if (x !== 0 || y !== 0 || z !== 0) {
  423. var tmat2 = TmpVectors.Matrix[1];
  424. Matrix.IdentityToRef(tmat2);
  425. tmat2.setTranslationFromFloats(x, y, z);
  426. tmat2.multiplyToRef(tmat, tmat);
  427. }
  428. if (parentBone) {
  429. tmat.multiplyToRef(parentBone.getAbsoluteTransform(), tmat);
  430. }
  431. tmat.multiplyToRef(meshMat, tmat);
  432. position.x = tmat.m[12];
  433. position.y = tmat.m[13];
  434. position.z = tmat.m[14];
  435. }
  436. private _getLinesForBonesWithLength(bones: Bone[], meshMat: Matrix): void {
  437. var len = bones.length;
  438. let mesh = this.mesh._effectiveMesh;
  439. var meshPos = mesh.position;
  440. let idx = 0;
  441. for (var i = 0; i < len; i++) {
  442. var bone = bones[i];
  443. var points = this._debugLines[idx];
  444. if (bone._index === -1 || (!this._boneIndices.has(bone.getIndex()) && !this.options.useAllBones)) {
  445. continue;
  446. }
  447. if (!points) {
  448. points = [Vector3.Zero(), Vector3.Zero()];
  449. this._debugLines[idx] = points;
  450. }
  451. this._getBonePosition(points[0], bone, meshMat);
  452. this._getBonePosition(points[1], bone, meshMat, 0, bone.length, 0);
  453. points[0].subtractInPlace(meshPos);
  454. points[1].subtractInPlace(meshPos);
  455. idx++;
  456. }
  457. }
  458. private _getLinesForBonesNoLength(bones: Bone[]): void {
  459. var len = bones.length;
  460. var boneNum = 0;
  461. let mesh = this.mesh._effectiveMesh;
  462. var meshPos = mesh.position;
  463. for (var i = len - 1; i >= 0; i--) {
  464. var childBone = bones[i];
  465. var parentBone = childBone.getParent();
  466. if (!parentBone || (!this._boneIndices.has(childBone.getIndex()) && !this.options.useAllBones)) {
  467. continue;
  468. }
  469. var points = this._debugLines[boneNum];
  470. if (!points) {
  471. points = [Vector3.Zero(), Vector3.Zero()];
  472. this._debugLines[boneNum] = points;
  473. }
  474. childBone.getAbsolutePositionToRef(mesh, points[0]);
  475. parentBone.getAbsolutePositionToRef(mesh, points[1]);
  476. points[0].subtractInPlace(meshPos);
  477. points[1].subtractInPlace(meshPos);
  478. boneNum++;
  479. }
  480. }
  481. /** function to revert the mesh and scene back to the initial state. */
  482. private _revert(animationState: boolean): void {
  483. if (this.options.pauseAnimations) {
  484. this.scene.animationsEnabled = animationState;
  485. this.utilityLayer!.utilityLayerScene!.animationsEnabled = animationState;
  486. }
  487. }
  488. /** function to get the absolute bind pose of a bone by accumulating transformations up the bone hierarchy. */
  489. private _getAbsoluteBindPoseToRef(bone: Nullable<Bone>, matrix: Matrix) {
  490. if (bone === null || bone._index === -1) {
  491. matrix.copyFrom(Matrix.Identity());
  492. return;
  493. }
  494. this._getAbsoluteBindPoseToRef(bone.getParent(), matrix);
  495. bone.getBindPose().multiplyToRef(matrix, matrix);
  496. return;
  497. }
  498. /** function to build and bind sphere joint points and spur bone representations. */
  499. private _buildSpheresAndSpurs(spheresOnly = true): void {
  500. if (this._debugMesh) {
  501. this._debugMesh.dispose();
  502. this._debugMesh = null;
  503. this.ready = false;
  504. }
  505. this._ready = false;
  506. let utilityLayerScene = this.utilityLayer?.utilityLayerScene!;
  507. let bones: Bone[] = this.skeleton.bones;
  508. let spheres: Array<[Mesh, Bone]> = [];
  509. let spurs: Mesh[] = [];
  510. const animationState = this.scene.animationsEnabled;
  511. try {
  512. if (this.options.pauseAnimations) {
  513. this.scene.animationsEnabled = false;
  514. utilityLayerScene.animationsEnabled = false;
  515. }
  516. if (this.options.returnToRest) {
  517. this.skeleton.returnToRest();
  518. }
  519. if (this.autoUpdateBonesMatrices) {
  520. this.skeleton.computeAbsoluteTransforms();
  521. }
  522. let longestBoneLength = Number.NEGATIVE_INFINITY;
  523. let displayOptions = this.options.displayOptions || {};
  524. for (let i = 0; i < bones.length; i++) {
  525. let bone = bones[i];
  526. if (bone._index === -1 || (!this._boneIndices.has(bone.getIndex()) && !this.options.useAllBones)) {
  527. continue;
  528. }
  529. let boneAbsoluteBindPoseTransform = new Matrix();
  530. this._getAbsoluteBindPoseToRef(bone, boneAbsoluteBindPoseTransform);
  531. let anchorPoint = new Vector3();
  532. boneAbsoluteBindPoseTransform.decompose(undefined, undefined, anchorPoint);
  533. bone.children.forEach((bc, i) => {
  534. let childAbsoluteBindPoseTransform : Matrix = new Matrix();
  535. bc.getBindPose().multiplyToRef(boneAbsoluteBindPoseTransform, childAbsoluteBindPoseTransform);
  536. let childPoint = new Vector3();
  537. childAbsoluteBindPoseTransform.decompose(undefined, undefined, childPoint);
  538. let distanceFromParent = Vector3.Distance(anchorPoint, childPoint);
  539. if (distanceFromParent > longestBoneLength) {
  540. longestBoneLength = distanceFromParent;
  541. }
  542. if (spheresOnly) {
  543. return;
  544. }
  545. let dir = childPoint.clone().subtract(anchorPoint.clone());
  546. let h = dir.length();
  547. let up = dir.normalize().scale(h);
  548. let midStep = displayOptions.midStep || 0.165;
  549. let midStepFactor = displayOptions.midStepFactor || 0.215;
  550. let up0 = up.scale(midStep);
  551. let spur = ShapeBuilder.ExtrudeShapeCustom('skeletonViewer',
  552. {
  553. shape: [
  554. new Vector3(1, -1, 0),
  555. new Vector3(1, 1, 0),
  556. new Vector3(-1, 1, 0),
  557. new Vector3(-1, -1, 0),
  558. new Vector3(1, -1, 0)
  559. ],
  560. path: [ Vector3.Zero(), up0, up ],
  561. scaleFunction:
  562. (i: number) => {
  563. switch (i){
  564. case 0:
  565. case 2:
  566. return 0;
  567. case 1:
  568. return h * midStepFactor;
  569. }
  570. return 0;
  571. },
  572. sideOrientation: Mesh.DEFAULTSIDE,
  573. updatable: false
  574. }, utilityLayerScene);
  575. let numVertices = spur.getTotalVertices();
  576. let mwk: number[] = [], mik: number[] = [];
  577. for (let i = 0; i < numVertices; i++) {
  578. mwk.push(1, 0, 0, 0);
  579. // Select verts at end of spur (ie vert 10 to 14) and bind to child
  580. // bone if spurFollowsChild is enabled.
  581. if (displayOptions.spurFollowsChild && i > 9) {
  582. mik.push(bc.getIndex(), 0, 0, 0);
  583. }
  584. else {
  585. mik.push(bone.getIndex(), 0, 0, 0);
  586. }
  587. }
  588. spur.position = anchorPoint.clone();
  589. spur.setVerticesData(VertexBuffer.MatricesWeightsKind, mwk, false);
  590. spur.setVerticesData(VertexBuffer.MatricesIndicesKind, mik, false);
  591. spur.convertToFlatShadedMesh();
  592. spurs.push(spur);
  593. });
  594. let sphereBaseSize = displayOptions.sphereBaseSize || 0.2;
  595. let sphere = SphereBuilder.CreateSphere('skeletonViewer', {
  596. segments: 6,
  597. diameter: sphereBaseSize,
  598. updatable: true
  599. }, utilityLayerScene);
  600. const numVertices = sphere.getTotalVertices();
  601. let mwk: number[] = [], mik: number[] = [];
  602. for (let i = 0; i < numVertices; i++) {
  603. mwk.push(1, 0, 0, 0);
  604. mik.push(bone.getIndex(), 0, 0, 0);
  605. }
  606. sphere.setVerticesData(VertexBuffer.MatricesWeightsKind, mwk, false);
  607. sphere.setVerticesData(VertexBuffer.MatricesIndicesKind, mik, false);
  608. sphere.position = anchorPoint.clone();
  609. spheres.push([sphere, bone]);
  610. }
  611. let sphereScaleUnit = displayOptions.sphereScaleUnit || 2;
  612. let sphereFactor = displayOptions.sphereFactor || 0.85;
  613. const meshes = [];
  614. for (let i = 0; i < spheres.length; i++) {
  615. let [sphere, bone] = spheres[i];
  616. let scale = 1 / (sphereScaleUnit / longestBoneLength);
  617. let _stepsOut = 0;
  618. let _b = bone;
  619. while ((_b.getParent()) && (_b.getParent() as Bone).getIndex() !== -1) {
  620. _stepsOut++;
  621. _b = (_b.getParent() as Bone);
  622. }
  623. sphere.scaling.scaleInPlace(scale * Math.pow(sphereFactor, _stepsOut));
  624. meshes.push(sphere);
  625. }
  626. this.debugMesh = Mesh.MergeMeshes(meshes.concat(spurs), true, true);
  627. if (this.debugMesh) {
  628. this.debugMesh.renderingGroupId = this.renderingGroupId;
  629. this.debugMesh.skeleton = this.skeleton;
  630. this.debugMesh.parent = this.mesh;
  631. this.debugMesh.computeBonesUsingShaders = this.options.computeBonesUsingShaders ?? true;
  632. this.debugMesh.alwaysSelectAsActiveMesh = true;
  633. }
  634. const light = this.utilityLayer!._getSharedGizmoLight();
  635. light.intensity = 0.7;
  636. this._revert(animationState);
  637. this.ready = true;
  638. } catch (err) {
  639. console.error(err);
  640. this._revert(animationState);
  641. this.dispose();
  642. }
  643. }
  644. private _buildLocalAxes(): void {
  645. if (this._localAxes) {
  646. this._localAxes.dispose();
  647. }
  648. this._localAxes = null;
  649. let displayOptions = this.options.displayOptions || {};
  650. if (!displayOptions.showLocalAxes) {
  651. return;
  652. }
  653. const targetScene = this._utilityLayer!.utilityLayerScene;
  654. const size = displayOptions.localAxesSize || 0.075;
  655. let lines = [];
  656. let colors = [];
  657. let red = new Color4(1, 0, 0, 1);
  658. let green = new Color4(0, 1, 0, 1);
  659. let blue = new Color4(0, 0, 1, 1);
  660. let mwk: number[] = [];
  661. let mik: number[] = [];
  662. const vertsPerBone = 6;
  663. for (let i in this.skeleton.bones) {
  664. let bone = this.skeleton.bones[i];
  665. if (bone._index === -1 || (!this._boneIndices.has(bone.getIndex()) && !this.options.useAllBones)) {
  666. continue;
  667. }
  668. let boneAbsoluteBindPoseTransform = new Matrix();
  669. let boneOrigin = new Vector3();
  670. this._getAbsoluteBindPoseToRef(bone, boneAbsoluteBindPoseTransform);
  671. boneAbsoluteBindPoseTransform.decompose(undefined, undefined, boneOrigin);
  672. let m = bone.getBindPose().getRotationMatrix();
  673. let boneAxisX = Vector3.TransformCoordinates(new Vector3(0 + size, 0, 0), m);
  674. let boneAxisY = Vector3.TransformCoordinates(new Vector3(0, 0 + size, 0), m);
  675. let boneAxisZ = Vector3.TransformCoordinates(new Vector3(0, 0, 0 + size), m);
  676. let axisX = [boneOrigin, boneOrigin.add(boneAxisX)];
  677. let axisY = [boneOrigin, boneOrigin.add(boneAxisY)];
  678. let axisZ = [boneOrigin, boneOrigin.add(boneAxisZ)];
  679. let linePoints = [axisX, axisY, axisZ];
  680. let lineColors = [[red, red], [green, green], [blue, blue]];
  681. lines.push(...linePoints);
  682. colors.push(...lineColors);
  683. for (let j = 0; j < vertsPerBone; j++) {
  684. mwk.push(1, 0, 0, 0);
  685. mik.push(bone.getIndex(), 0, 0, 0);
  686. }
  687. }
  688. this._localAxes = LinesBuilder.CreateLineSystem('localAxes', { lines: lines, colors: colors, updatable: true }, targetScene);
  689. this._localAxes.setVerticesData(VertexBuffer.MatricesWeightsKind, mwk, false);
  690. this._localAxes.setVerticesData(VertexBuffer.MatricesIndicesKind, mik, false);
  691. this._localAxes.skeleton = this.skeleton;
  692. this._localAxes.renderingGroupId = this.renderingGroupId;
  693. this._localAxes.parent = this.mesh;
  694. this._localAxes.computeBonesUsingShaders = this.options.computeBonesUsingShaders ?? true;
  695. }
  696. /** Update the viewer to sync with current skeleton state, only used for the line display. */
  697. private _displayLinesUpdate(): void {
  698. if (!this._utilityLayer) {
  699. return;
  700. }
  701. if (this.autoUpdateBonesMatrices) {
  702. this.skeleton.computeAbsoluteTransforms();
  703. }
  704. let mesh = this.mesh._effectiveMesh;
  705. if (this.skeleton.bones[0].length === undefined) {
  706. this._getLinesForBonesNoLength(this.skeleton.bones);
  707. } else {
  708. this._getLinesForBonesWithLength(this.skeleton.bones, mesh.getWorldMatrix());
  709. }
  710. const targetScene = this._utilityLayer.utilityLayerScene;
  711. if (targetScene) {
  712. if (!this._debugMesh) {
  713. this._debugMesh = LinesBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: null }, targetScene);
  714. this._debugMesh.renderingGroupId = this.renderingGroupId;
  715. } else {
  716. LinesBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: this._debugMesh }, targetScene);
  717. }
  718. this._debugMesh.position.copyFrom(this.mesh.position);
  719. this._debugMesh.color = this.color;
  720. }
  721. }
  722. /** Changes the displayMode of the skeleton viewer
  723. * @param mode The displayMode numerical value
  724. */
  725. public changeDisplayMode(mode: number): void {
  726. let wasEnabled = (this.isEnabled) ? true : false;
  727. if (this.displayMode !== mode) {
  728. this.isEnabled = false;
  729. if (this._debugMesh) {
  730. this._debugMesh.dispose();
  731. this._debugMesh = null;
  732. this.ready = false;
  733. }
  734. this.displayMode = mode;
  735. this.update();
  736. this._bindObs();
  737. this.isEnabled = wasEnabled;
  738. }
  739. }
  740. /** Sets a display option of the skeleton viewer
  741. *
  742. * | Option | Type | Default | Description |
  743. * | ---------------- | ------- | ------- | ----------- |
  744. * | midStep | float | 0.235 | A percentage between a bone and its child that determines the widest part of a spur. Only used when `displayMode` is set to `DISPLAY_SPHERE_AND_SPURS`. |
  745. * | midStepFactor | float | 0.15 | Mid step width expressed as a factor of the length. A value of 0.5 makes the spur width half of the spur length. Only used when `displayMode` is set to `DISPLAY_SPHERE_AND_SPURS`. |
  746. * | sphereBaseSize | float | 2 | Sphere base size. Only used when `displayMode` is set to `DISPLAY_SPHERE_AND_SPURS`. |
  747. * | sphereScaleUnit | float | 0.865 | Sphere scale factor used to scale spheres in relation to the longest bone. Only used when `displayMode` is set to `DISPLAY_SPHERE_AND_SPURS`. |
  748. * | spurFollowsChild | boolean | false | Whether a spur should attach its far end to the child bone. |
  749. * | showLocalAxes | boolean | false | Displays local axes on all bones. |
  750. * | localAxesSize | float | 0.075 | Determines the length of each local axis. |
  751. *
  752. * @param option String of the option name
  753. * @param value The numerical option value
  754. */
  755. public changeDisplayOptions(option: string, value: number): void {
  756. let wasEnabled = (this.isEnabled) ? true : false;
  757. (this.options.displayOptions as any)[option] = value;
  758. this.isEnabled = false;
  759. if (this._debugMesh) {
  760. this._debugMesh.dispose();
  761. this._debugMesh = null;
  762. this.ready = false;
  763. }
  764. this.update();
  765. this._bindObs();
  766. this.isEnabled = wasEnabled;
  767. }
  768. /** Release associated resources */
  769. public dispose(): void {
  770. this.isEnabled = false;
  771. if (this._debugMesh) {
  772. this._debugMesh.dispose();
  773. this._debugMesh = null;
  774. }
  775. if (this._utilityLayer) {
  776. this._utilityLayer.dispose();
  777. this._utilityLayer = null;
  778. }
  779. this.ready = false;
  780. }
  781. }