skeletonViewer.js 37 KB

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