import { Color3, Vector3, Matrix, Tmp } from "Math"; import { Scene } from "scene"; import { Nullable } from "types"; import { LinesMesh } from "Mesh"; import { Skeleton, Bone } from "Bones"; import { AbstractMesh } from "Mesh"; import { UtilityLayerRenderer } from "index"; /** * Class used to render a debug view of a given skeleton * @see http://www.babylonjs-playground.com/#1BZJVJ#8 */ export class SkeletonViewer { /** Gets or sets the color used to render the skeleton */ public color: Color3 = Color3.White(); private _scene: Scene; private _debugLines = new Array>(); private _debugMesh: Nullable; private _isEnabled = false; private _renderFunction: () => void; public get debugMesh(): Nullable { return this._debugMesh; } /** * Creates a new SkeletonViewer * @param skeleton defines the skeleton to render * @param mesh defines the mesh attached to the skeleton * @param scene defines the hosting scene * @param autoUpdateBonesMatrices defines a boolean indicating if bones matrices must be forced to update before rendering (true by default) * @param renderingGroupId defines the rendering group id to use with the viewer * @param utilityLayerRenderer defines an optional utility layer to render the helper on */ constructor( /** defines the skeleton to render */ public skeleton: Skeleton, /** defines the mesh attached to the skeleton */ public mesh: AbstractMesh, scene: Scene, /** defines a boolean indicating if bones matrices must be forced to update before rendering (true by default) */ public autoUpdateBonesMatrices = true, /** defines the rendering group id to use with the viewer */ public renderingGroupId = 1, /** defines an optional utility layer to render the helper on */ public utilityLayerRenderer?: UtilityLayerRenderer ) { this._scene = scene; this.update(); this._renderFunction = this.update.bind(this); } /** Gets or sets a boolean indicating if the viewer is enabled */ public set isEnabled(value: boolean) { if (this._isEnabled === value) { return; } this._isEnabled = value; if (value) { this._scene.registerBeforeRender(this._renderFunction); } else { this._scene.unregisterBeforeRender(this._renderFunction); } } public get isEnabled(): boolean { return this._isEnabled; } private _getBonePosition(position: Vector3, bone: Bone, meshMat: Matrix, x = 0, y = 0, z = 0): void { var tmat = Tmp.Matrix[0]; var parentBone = bone.getParent(); tmat.copyFrom(bone.getLocalMatrix()); if (x !== 0 || y !== 0 || z !== 0) { var tmat2 = Tmp.Matrix[1]; Matrix.IdentityToRef(tmat2); tmat2.setTranslationFromFloats(x, y, z); tmat2.multiplyToRef(tmat, tmat); } if (parentBone) { tmat.multiplyToRef(parentBone.getAbsoluteTransform(), tmat); } tmat.multiplyToRef(meshMat, tmat); position.x = tmat.m[12]; position.y = tmat.m[13]; position.z = tmat.m[14]; } private _getLinesForBonesWithLength(bones: Bone[], meshMat: Matrix): void { var len = bones.length; var meshPos = this.mesh.position; for (var i = 0; i < len; i++) { var bone = bones[i]; var points = this._debugLines[i]; if (!points) { points = [Vector3.Zero(), Vector3.Zero()]; this._debugLines[i] = points; } this._getBonePosition(points[0], bone, meshMat); this._getBonePosition(points[1], bone, meshMat, 0, bone.length, 0); points[0].subtractInPlace(meshPos); points[1].subtractInPlace(meshPos); } } private _getLinesForBonesNoLength(bones: Bone[], meshMat: Matrix): void { var len = bones.length; var boneNum = 0; var meshPos = this.mesh.position; for (var i = len - 1; i >= 0; i--) { var childBone = bones[i]; var parentBone = childBone.getParent(); if (!parentBone) { continue; } var points = this._debugLines[boneNum]; if (!points) { points = [Vector3.Zero(), Vector3.Zero()]; this._debugLines[boneNum] = points; } childBone.getAbsolutePositionToRef(this.mesh, points[0]); parentBone.getAbsolutePositionToRef(this.mesh, points[1]); points[0].subtractInPlace(meshPos); points[1].subtractInPlace(meshPos); boneNum++; } } /** Update the viewer to sync with current skeleton state */ public update() { if (this.autoUpdateBonesMatrices) { this.skeleton.computeAbsoluteTransforms(); } if (this.skeleton.bones[0].length === undefined) { this._getLinesForBonesNoLength(this.skeleton.bones, this.mesh.getWorldMatrix()); } else { this._getLinesForBonesWithLength(this.skeleton.bones, this.mesh.getWorldMatrix()); } const targetScene = this.utilityLayerRenderer ? this.utilityLayerRenderer.utilityLayerScene : this._scene; if (!this._debugMesh) { this._debugMesh = MeshBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: null }, targetScene); this._debugMesh.renderingGroupId = this.renderingGroupId; } else { MeshBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: this._debugMesh }, targetScene); } this._debugMesh.position.copyFrom(this.mesh.position); this._debugMesh.color = this.color; } /** Release associated resources */ public dispose() { if (this._debugMesh) { this.isEnabled = false; this._debugMesh.dispose(); this._debugMesh = null; } } }