|
@@ -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();
|