Преглед изворни кода

Merge pull request #8729 from Pryme8/p8/SkeletonViewer/ShaderDebugUpdates

Skeleton Viewer Shader Updates
sebavan пре 5 година
родитељ
комит
cb982e95fb

+ 90 - 3
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/meshPropertyGridComponent.tsx

@@ -33,6 +33,8 @@ import { RenderingManager } from 'babylonjs/Rendering/renderingManager';
 import { CommonPropertyGridComponent } from '../commonPropertyGridComponent';
 import { VariantsPropertyGridComponent } from '../variantsPropertyGridComponent';
 import { HexLineComponent } from '../../../lines/hexLineComponent';
+import { SkeletonViewer } from 'babylonjs/Debug/skeletonViewer';
+import { ShaderMaterial } from 'babylonjs/Materials/shaderMaterial';
 
 interface IMeshPropertyGridComponentProps {
     globalState: GlobalState;
@@ -44,14 +46,19 @@ interface IMeshPropertyGridComponentProps {
 
 export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGridComponentProps, {
     displayNormals: boolean,
-    displayVertexColors: boolean
+    displayVertexColors: boolean,
+    displayBoneWeights: boolean,
+    displayBoneIndex: number,
+    displaySkeletonMap:boolean
 }> {
     constructor(props: IMeshPropertyGridComponentProps) {
         super(props);
-
         this.state = {
             displayNormals: false,
-            displayVertexColors: false
+            displayVertexColors: false,
+            displayBoneWeights: false,
+            displayBoneIndex: 0,
+            displaySkeletonMap: false
         };
     }
 
@@ -165,6 +172,7 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
             if (!mesh.reservedDataStore.originalMaterial) {
                 mesh.reservedDataStore.originalMaterial = mesh.material;
             }
+
             const normalMaterial = new (BABYLON as any).NormalMaterial("normalMaterial", scene);
             normalMaterial.disableLighting = true;
             if (mesh.material) {
@@ -208,6 +216,69 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
         }
     }
 
+    displayBoneWeights() {
+        const mesh = this.props.mesh;
+        const scene = mesh.getScene();
+
+        if (mesh.material && mesh.material.getClassName() === "BoneWeightShader") {
+            mesh.material.dispose();
+            mesh.material = mesh.reservedDataStore.originalMaterial;
+            mesh.reservedDataStore.originalMaterial = null;
+            this.setState({ displayBoneWeights: false });
+        } else {
+          
+            if (!mesh.reservedDataStore) {
+                mesh.reservedDataStore = {};
+            }
+            if (!mesh.reservedDataStore.originalMaterial) {
+                mesh.reservedDataStore.originalMaterial = mesh.material;
+            }
+            if (!mesh.reservedDataStore.displayBoneIndex) {
+                mesh.reservedDataStore.displayBoneIndex = this.state.displayBoneIndex;
+            }
+            if (mesh.skeleton){
+                const boneWeightsShader = SkeletonViewer.CreateBoneWeightShader({skeleton:mesh.skeleton}, scene)
+                boneWeightsShader.reservedDataStore = { hidden: true };
+                mesh.material = boneWeightsShader;
+                this.setState({ displayBoneWeights: true });
+            }            
+        }
+    }
+
+    displaySkeletonMap() {
+        const mesh = this.props.mesh;
+        const scene = mesh.getScene();
+
+        if (mesh.material && mesh.material.getClassName() === "SkeletonMapShader") {
+            mesh.material.dispose();
+            mesh.material = mesh.reservedDataStore.originalMaterial;
+            mesh.reservedDataStore.originalMaterial = null;
+            this.setState({ displaySkeletonMap: false });
+        } else {          
+            if (!mesh.reservedDataStore) {
+                mesh.reservedDataStore = {};
+            }
+            if (!mesh.reservedDataStore.originalMaterial) {
+                mesh.reservedDataStore.originalMaterial = mesh.material;
+            }  
+            if (mesh.skeleton){
+                const skeletonMapShader = SkeletonViewer.CreateSkeletonMapShader({skeleton:mesh.skeleton}, scene)
+                skeletonMapShader.reservedDataStore = { hidden: true };
+                mesh.material = skeletonMapShader;
+                this.setState({ displaySkeletonMap: true });
+            }            
+        }
+    }
+
+    onBoneDisplayIndexChange(value:number):void{
+        let mesh = this.props.mesh
+        mesh.reservedDataStore.displayBoneIndex = value
+        this.setState({ displayBoneIndex: value });
+        if (mesh.material && mesh.material.getClassName() === "BoneWeightShader") {
+            (mesh.material as ShaderMaterial).setFloat('targetBoneIndex', value)
+        }
+    }
+
     onMaterialLink() {
         if (!this.props.onSelectionChangedObservable) {
             return;
@@ -273,6 +344,8 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
         const displayVertexColors = !!(mesh.material != null && mesh.material.reservedDataStore && mesh.material.reservedDataStore.isVertexColorMaterial);
         const renderNormalVectors = (mesh.reservedDataStore && mesh.reservedDataStore.normalLines) ? true : false;
         const renderWireframeOver = (mesh.reservedDataStore && mesh.reservedDataStore.wireframeOver) ? true : false;
+        const displayBoneWeights = mesh.material != null && mesh.material.getClassName() === "BoneWeightShader";
+        const displaySkeletonMap = mesh.material != null && mesh.material.getClassName() === "SkeletonMapShader";
 
         var morphTargets: MorphTarget[] = [];
 
@@ -480,6 +553,20 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
                         !mesh.isAnInstance &&
                         <CheckBoxLineComponent label="Render wireframe over mesh" isSelected={() => renderWireframeOver} onSelect={() => this.renderWireframeOver()} />
                     }
+                    {
+                        !mesh.isAnInstance && mesh.skeleton &&
+                        <CheckBoxLineComponent label="Display BoneWeights" isSelected={() => displayBoneWeights} onSelect={() => this.displayBoneWeights()} />
+                    }
+                    {
+                        !mesh.isAnInstance && this.state.displayBoneWeights && mesh.skeleton &&
+                        <SliderLineComponent label="Target Bone" decimalCount={0} target={mesh.reservedDataStore} propertyName="displayBoneIndex" minimum={0} maximum={mesh.skeleton.bones.length-1 || 0} step={1} onChange={(value)=>{this.onBoneDisplayIndexChange(value)}} />
+                        
+                    }
+                    {
+                        !mesh.isAnInstance && mesh.skeleton &&
+                        <CheckBoxLineComponent label="Display SkeletonMap" isSelected={() => displaySkeletonMap } onSelect={() => this.displaySkeletonMap()} />
+                    }                
+
                 </LineContainerComponent>
             </div>
         );

+ 52 - 0
src/Debug/ISkeletonViewer.ts

@@ -1,3 +1,6 @@
+import { Skeleton } from "../Bones/skeleton";
+import { Color3 } from '../Maths/math.color';
+
 /**
  * Defines the options associated with the creation of a SkeletonViewer.
  */
@@ -16,6 +19,9 @@ export interface ISkeletonViewerOptions{
 
    /** Flag to toggle if the Viewer should use the CPU for animations or not? */
    computeBonesUsingShaders : boolean;
+
+   /** Flag ignore non weighted bones */
+   useAllBones: boolean;
 }
 
 /**
@@ -36,4 +42,50 @@ export interface ISkeletonViewerDisplayOptions{
 
    /** Ratio for the Sphere Size */
    sphereFactor? : number;
+}
+
+/**
+ * Defines the constructor options for the BoneWeight Shader.
+ */
+export interface IBoneWeightShaderOptions{
+   /** Skeleton to Map */
+   skeleton: Skeleton;
+
+   /** Colors for Uninfluenced bones */
+   colorBase? : Color3;
+
+   /** Colors for 0.0-0.25 Weight bones */
+   colorZero? : Color3;
+
+   /** Color for 0.25-0.5 Weight Influence */
+   colorQuarter? : Color3;
+
+   /** Color for 0.5-0.75 Weight Influence */
+   colorHalf? : Color3;
+
+   /** Color for 0.75-1 Weight Influence */
+   colorFull? : Color3;
+
+   /** Color for Zero Weight Influence */
+   targetBoneIndex? : number;
+}
+
+/**
+ * Simple structure of the gradient steps for the Color Map.
+ */
+export interface ISkeletonMapShaderColorMapKnot{
+   /** Color of the Knot */
+   color : Color3;
+   /** Location of the Knot */
+   location : number;
+}
+
+/**
+ * Defines the constructor options for the SkeletonMap Shader.
+ */
+export interface ISkeletonMapShaderOptions{
+   /** Skeleton to Map */
+   skeleton: Skeleton;
+   /** Array of ColorMapKnots that make the gradient must be ordered with knot[i].location < knot[i+1].location*/
+   colorMap? : ISkeletonMapShaderColorMapKnot[];
 }

+ 278 - 19
src/Debug/skeletonViewer.ts

@@ -9,10 +9,14 @@ import { Mesh } from "../Meshes/mesh";
 import { LinesMesh } from "../Meshes/linesMesh";
 import { LinesBuilder } from "../Meshes/Builders/linesBuilder";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
+import { Material } from '../Materials/material';
 import { StandardMaterial } from '../Materials/standardMaterial';
+import { ShaderMaterial } from '../Materials/shaderMaterial';
+import { DynamicTexture } from '../Materials/Textures/dynamicTexture';
 import { VertexBuffer } from '../Meshes/buffer';
+import { Effect } from '../Materials/effect';
 
-import { ISkeletonViewerOptions } from './ISkeletonViewer';
+import { ISkeletonViewerOptions, IBoneWeightShaderOptions, ISkeletonMapShaderOptions, ISkeletonMapShaderColorMapKnot } from './ISkeletonViewer';
 import { Observer } from '../Misc/observable';
 
 import { SphereBuilder } from '../Meshes/Builders/sphereBuilder';
@@ -30,6 +34,261 @@ export class SkeletonViewer {
     /** public Display constants BABYLON.SkeletonViewer.DISPLAY_SPHERE_AND_SPURS */
     public static readonly DISPLAY_SPHERE_AND_SPURS = 2;
 
+    /** public static method to create a BoneWeight Shader
+     * @param options The constructor options
+     * @param scene The scene that the shader is scoped to
+     * @returns The created ShaderMaterial
+     * @see http://www.babylonjs-playground.com/#1BZJVJ#395
+     */
+    static CreateBoneWeightShader(options: IBoneWeightShaderOptions, scene: Scene): ShaderMaterial {
+
+        let skeleton: Skeleton = options.skeleton;
+        let colorBase: Color3 = options.colorBase ?? Color3.Black();
+        let colorZero: Color3 = options.colorZero ?? Color3.Blue();
+        let colorQuarter: Color3 = options.colorQuarter ?? Color3.Green();
+        let colorHalf: Color3 = options.colorHalf ?? Color3.Yellow();
+        let colorFull: Color3 = options.colorFull ?? Color3.Red();
+        let targetBoneIndex: number = options.targetBoneIndex ?? 0;
+
+        Effect.ShadersStore['boneWeights:' + skeleton.name + "VertexShader"] =
+        `precision highp float;
+
+        attribute vec3 position;
+        attribute vec2 uv;
+
+        uniform mat4 view;
+        uniform mat4 projection;
+        uniform mat4 worldViewProjection;
+
+        #include<bonesDeclaration>
+        #include<instancesDeclaration>
+
+        varying vec3 vColor;
+
+        uniform vec3 colorBase;
+        uniform vec3 colorZero;
+        uniform vec3 colorQuarter;
+        uniform vec3 colorHalf;
+        uniform vec3 colorFull;
+
+        uniform float targetBoneIndex;
+
+        void main() {
+            vec3 positionUpdated = position;
+
+            #include<instancesVertex>
+            #include<bonesVertex>
+
+            vec4 worldPos = finalWorld * vec4(positionUpdated, 1.0);
+
+            vec3 color = colorBase;
+            float totalWeight = 0.;
+            if(matricesIndices[0] == targetBoneIndex && matricesWeights[0] > 0.){
+                totalWeight += matricesWeights[0];
+            }
+            if(matricesIndices[1] == targetBoneIndex && matricesWeights[1] > 0.){
+                totalWeight += matricesWeights[1];
+            }
+            if(matricesIndices[2] == targetBoneIndex && matricesWeights[2] > 0.){
+                totalWeight += matricesWeights[2];
+            }
+            if(matricesIndices[3] == targetBoneIndex && matricesWeights[3] > 0.){
+                totalWeight += matricesWeights[3];
+            }
+
+            color = mix(color, colorZero, smoothstep(0., 0.25, totalWeight));
+            color = mix(color, colorQuarter, smoothstep(0.25, 0.5, totalWeight));
+            color = mix(color, colorHalf, smoothstep(0.5, 0.75, totalWeight));
+            color = mix(color, colorFull, smoothstep(0.75, 1.0, totalWeight));
+            vColor = color;
+
+        gl_Position = projection * view * worldPos;
+        }`;
+        Effect.ShadersStore['boneWeights:' + skeleton.name + "FragmentShader"] =
+        `
+            precision highp float;
+            varying vec3 vPosition;
+
+            varying vec3 vColor;
+
+            void main() {
+                vec4 color = vec4(vColor, 1.0);
+                gl_FragColor = color;
+            }
+        `;
+        let shader: ShaderMaterial = new ShaderMaterial('boneWeight:' + skeleton.name, scene,
+        {
+            vertex: 'boneWeights:' + skeleton.name,
+            fragment: 'boneWeights:' + skeleton.name
+        },
+        {
+            attributes: ['position', 'normal'],
+            uniforms: [
+                'world', 'worldView', 'worldViewProjection', 'view', 'projection', 'viewProjection',
+                'colorBase', 'colorZero', 'colorQuarter', 'colorHalf', 'colorFull', 'targetBoneIndex'
+            ]
+        });
+
+        shader.setColor3('colorBase', colorBase);
+        shader.setColor3('colorZero', colorZero);
+        shader.setColor3('colorQuarter', colorQuarter);
+        shader.setColor3('colorHalf', colorHalf);
+        shader.setColor3('colorFull', colorFull);
+        shader.setFloat('targetBoneIndex', targetBoneIndex);
+
+        shader.getClassName = (): string => {
+            return "BoneWeightShader";
+        };
+
+        shader.transparencyMode = Material.MATERIAL_OPAQUE;
+
+        return shader;
+    }
+
+    /** public static method to create a BoneWeight Shader
+     * @param options The constructor options
+     * @param scene The scene that the shader is scoped to
+     * @returns The created ShaderMaterial
+     */
+    static CreateSkeletonMapShader(options: ISkeletonMapShaderOptions, scene: Scene) {
+
+        let skeleton: Skeleton = options.skeleton;
+        let colorMap: ISkeletonMapShaderColorMapKnot[] = options.colorMap ?? [
+            {
+                color: new Color3(1, 0.38, 0.18),
+                location : 0
+            },
+            {
+                color: new Color3(.59, 0.18, 1.00),
+                location : 0.2
+            },
+            {
+                color: new Color3(0.59, 1, 0.18),
+                location : 0.4
+            },
+            {
+               color: new Color3(1, 0.87, 0.17),
+                location : 0.6
+            },
+            {
+                color: new Color3(1, 0.17, 0.42),
+                location : 0.8
+            },
+            {
+                color: new Color3(0.17, 0.68, 1.0),
+                location : 1.0
+            }
+        ];
+
+        let bufferWidth: number = skeleton.bones.length + 1;
+        let colorMapBuffer: number[] = SkeletonViewer._CreateBoneMapColorBuffer(bufferWidth, colorMap, scene);
+        let shader = new ShaderMaterial('boneWeights:' + skeleton.name, scene,
+        {
+            vertexSource:
+            `precision highp float;
+
+            attribute vec3 position;
+            attribute vec2 uv;
+
+            uniform mat4 view;
+            uniform mat4 projection;
+            uniform mat4 worldViewProjection;
+            uniform float colorMap[` + ((skeleton.bones.length) * 4) + `];
+
+            #include<bonesDeclaration>
+            #include<instancesDeclaration>
+
+            varying vec3 vColor;
+
+            void main() {
+                vec3 positionUpdated = position;
+
+                #include<instancesVertex>
+                #include<bonesVertex>
+
+                vec3 color = vec3(0.);
+                bool first = true;
+
+                for (int i = 0; i < 4; i++) {
+                    int boneIdx = int(matricesIndices[i]);
+                    float boneWgt = matricesWeights[i];
+
+                    vec3 c = vec3(colorMap[boneIdx * 4 + 0], colorMap[boneIdx * 4 + 1], colorMap[boneIdx * 4 + 2]);
+
+                    if (boneWgt > 0.) {
+                        if (first) {
+                            first = false;
+                            color = c;
+                        } else {
+                            color = mix(color, c, boneWgt);
+                        }
+                    }
+                }
+
+                vColor = color;
+
+                vec4 worldPos = finalWorld * vec4(positionUpdated, 1.0);
+
+                gl_Position = projection * view * worldPos;
+            }`,
+            fragmentSource:
+            `
+            precision highp float;
+            varying vec3 vColor;
+
+            void main() {
+                vec4 color = vec4( vColor, 1.0 );
+                gl_FragColor = color;
+            }
+            `
+        },
+        {
+            attributes: ['position', 'normal'],
+            uniforms: [
+                'world', 'worldView', 'worldViewProjection', 'view', 'projection', 'viewProjection',
+                'colorMap'
+            ]
+        });
+
+        shader.setFloats('colorMap', colorMapBuffer);
+
+        shader.getClassName = (): string => {
+            return "SkeletonMapShader";
+        };
+
+        shader.transparencyMode = Material.MATERIAL_OPAQUE;
+
+        return shader;
+    }
+
+    /** private static method to create a BoneWeight Shader
+     * @param size The size of the buffer to create (usually the bone count)
+     * @param colorMap The gradient data to generate
+     * @param scene The scene that the shader is scoped to
+     * @returns an Array of floats from the color gradient values
+     */
+    private static _CreateBoneMapColorBuffer(size: number, colorMap: ISkeletonMapShaderColorMapKnot[], scene: Scene) {
+        let tempGrad = new DynamicTexture('temp', {width: size, height: 1}, scene, false);
+        let ctx = tempGrad.getContext();
+        let grad = ctx.createLinearGradient(0, 0, size, 0);
+
+        colorMap.forEach((stop) => {
+            grad.addColorStop(stop.location, stop.color.toHexString());
+        });
+
+        ctx.fillStyle = grad;
+        ctx.fillRect(0, 0, size, 1);
+        tempGrad.update();
+        let buffer: number[] = [];
+        let data: Uint8ClampedArray = ctx.getImageData(0, 0, size, 1).data;
+        let rUnit = 1 / 255;
+        for (let i = 0; i < data.length; i++) {
+            buffer.push(data[i] * rUnit);
+        }
+        tempGrad.dispose();
+        return buffer;
+    }
+
     /** If SkeletonViewer scene scope. */
     private _scene : Scene;
 
@@ -99,7 +358,6 @@ export class SkeletonViewer {
         }
         this.options.displayMode = value;
     }
-
     /**
      * Creates a new SkeletonViewer
      * @param skeleton defines the skeleton to render
@@ -138,18 +396,19 @@ export class SkeletonViewer {
         options.displayOptions.sphereScaleUnit = options.displayOptions.sphereScaleUnit ?? 2;
         options.displayOptions.sphereFactor = options.displayOptions.sphereFactor ?? 0.865;
         options.computeBonesUsingShaders = options.computeBonesUsingShaders ?? true;
+        options.useAllBones = options.useAllBones ?? true;
 
-        const boneIndices = mesh.getVerticesData(VertexBuffer.MatricesIndicesKind);
-        const boneWeights = mesh.getVerticesData(VertexBuffer.MatricesWeightsKind);
-
+        const initialMeshBoneIndices = mesh.getVerticesData(VertexBuffer.MatricesIndicesKind);
+        const initialMeshBoneWeights = mesh.getVerticesData(VertexBuffer.MatricesWeightsKind);
         this._boneIndices = new Set();
 
-        if (boneIndices && boneWeights) {
-            for (let i = 0; i < boneIndices.length; ++i) {
-                const index = boneIndices[i], weight = boneWeights[i];
-
-                if (weight !== 0) {
-                    this._boneIndices.add(index);
+        if (!options.useAllBones) {
+            if (initialMeshBoneIndices && initialMeshBoneWeights) {
+                for (let i = 0; i < initialMeshBoneIndices.length; ++i) {
+                    const index = initialMeshBoneIndices[i], weight = initialMeshBoneWeights[i];
+                    if (weight !== 0) {
+                        this._boneIndices.add(index);
+                    }
                 }
             }
         }
@@ -255,7 +514,8 @@ export class SkeletonViewer {
         for (var i = 0; i < len; i++) {
             var bone = bones[i];
             var points = this._debugLines[idx];
-            if (bone._index === -1 || !this._boneIndices.has(bone.getIndex())) {
+
+            if (bone._index === -1 || (!this._boneIndices.has(bone.getIndex()) && !this.options.useAllBones)) {
                 continue;
             }
             if (!points) {
@@ -279,7 +539,7 @@ export class SkeletonViewer {
         for (var i = len - 1; i >= 0; i--) {
             var childBone = bones[i];
             var parentBone = childBone.getParent();
-            if (!parentBone || !this._boneIndices.has(childBone.getIndex())) {
+            if (!parentBone || (!this._boneIndices.has(childBone.getIndex()) && !this.options.useAllBones)) {
                 continue;
             }
             var points = this._debugLines[boneNum];
@@ -348,7 +608,7 @@ export class SkeletonViewer {
             for (let i = 0; i < bones.length; i++) {
                 let bone = bones[i];
 
-                if (bone._index === -1 || !this._boneIndices.has(bone.getIndex())) {
+                if (bone._index === -1 || (!this._boneIndices.has(bone.getIndex()) && !this.options.useAllBones)) {
                     continue;
                 }
 
@@ -356,6 +616,7 @@ export class SkeletonViewer {
                 getAbsoluteRestPose(bone, boneAbsoluteRestTransform);
 
                 let anchorPoint = new Vector3();
+
                 boneAbsoluteRestTransform.decompose(undefined, undefined, anchorPoint);
 
                 bone.children.forEach((bc, i) => {
@@ -363,9 +624,7 @@ export class SkeletonViewer {
                     bc.getRestPose().multiplyToRef(boneAbsoluteRestTransform, childAbsoluteRestTransform);
                     let childPoint = new Vector3();
                     childAbsoluteRestTransform.decompose(undefined, undefined, childPoint);
-
                     let distanceFromParent = Vector3.Distance(anchorPoint, childPoint);
-
                     if (distanceFromParent > longestBoneLength) {
                         longestBoneLength = distanceFromParent;
                     }
@@ -382,7 +641,7 @@ export class SkeletonViewer {
 
                     let up0 = up.scale(midStep);
 
-                    let spur = ShapeBuilder.ExtrudeShapeCustom(bc.name + ':spur',
+                    let spur = ShapeBuilder.ExtrudeShapeCustom('skeletonViewer',
                     {
                         shape:  [
                                     new Vector3(1, -1,  0),
@@ -426,10 +685,10 @@ export class SkeletonViewer {
 
                 let sphereBaseSize = displayOptions.sphereBaseSize || 0.2;
 
-                let sphere = SphereBuilder.CreateSphere(bone.name + ':sphere', {
+                let sphere = SphereBuilder.CreateSphere('skeletonViewer', {
                     segments: 6,
                     diameter: sphereBaseSize,
-                    updatable: false
+                    updatable: true
                 }, scene);
 
                 const numVertices = sphere.getTotalVertices();

+ 0 - 3
src/Materials/shaderMaterial.ts

@@ -540,7 +540,6 @@ export class ShaderMaterial extends Material {
 
         // Bones
         let numInfluencers = 0;
-
         if (mesh && mesh.useBones && mesh.computeBonesUsingShaders && mesh.skeleton) {
             attribs.push(VertexBuffer.MatricesIndicesKind);
             attribs.push(VertexBuffer.MatricesWeightsKind);
@@ -552,7 +551,6 @@ export class ShaderMaterial extends Material {
             const skeleton = mesh.skeleton;
 
             numInfluencers = mesh.numBoneInfluencers;
-
             defines.push("#define NUM_BONE_INFLUENCERS " + numInfluencers);
             fallbacks.addCPUSkinningFallback(0, mesh);
 
@@ -573,7 +571,6 @@ export class ShaderMaterial extends Material {
                     this._options.uniforms.push("mBones");
                 }
             }
-
         } else {
             defines.push("#define NUM_BONE_INFLUENCERS 0");
         }