All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jme3.scene.plugins.fbx.objects.FbxSkin Maven / Gradle / Ivy

There is a newer version: 3.7.0-stable
Show newest version
package com.jme3.scene.plugins.fbx.objects;

import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;

import com.jme3.asset.AssetLoadException;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.util.BufferUtils;
import com.jme3.scene.plugins.fbx.SceneLoader;
import com.jme3.scene.plugins.fbx.file.FbxElement;

public class FbxSkin extends FbxObject {

    public String skinningType;
    public List toSkin = new ArrayList<>();
    public List bones = new ArrayList<>();

    public FbxSkin(SceneLoader scene, FbxElement element) {
        super(scene, element);
        for(FbxElement e : element.children) {
            switch(e.id) {
            case "SkinningType":
                skinningType = (String) e.properties.get(0);
                break;
            }
        }
    }
    
    @Override
    public void link(FbxObject otherObject) {
        if(otherObject instanceof FbxCluster) {
            FbxCluster cluster = ((FbxCluster) otherObject);
            cluster.skin = this;
        }
    }
    
    public void generateSkinning() {
        for(FbxMesh fbxMesh : toSkin) {
            if(fbxMesh.geometries == null)
                continue;
            Mesh firstMesh = fbxMesh.geometries.get(0).getMesh();
            int maxWeightsPerVert = generateBoneData(firstMesh, fbxMesh);
            for(int i = 0; i < fbxMesh.geometries.size(); ++i) {
                Mesh mesh = fbxMesh.geometries.get(i).getMesh();
                if(mesh != firstMesh) {
                    mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.BoneWeight));
                    mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.BoneIndex));
                    mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.HWBoneWeight));
                    mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.HWBoneIndex));
                }
                mesh.setMaxNumWeights(maxWeightsPerVert);
                mesh.generateBindPose(true);
            }
        }
    }
    
    private int generateBoneData(Mesh mesh, FbxMesh fbxMesh) {
        // Create bone buffers
        FloatBuffer boneWeightData = BufferUtils.createFloatBuffer(fbxMesh.vCount * 4);
        ByteBuffer boneIndicesData = BufferUtils.createByteBuffer(fbxMesh.vCount * 4);
        mesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, boneWeightData);
        mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, boneIndicesData);
        mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(Usage.CpuOnly);
        mesh.getBuffer(VertexBuffer.Type.BoneIndex).setUsage(Usage.CpuOnly);
        VertexBuffer weightsHW = new VertexBuffer(Type.HWBoneWeight);
        VertexBuffer indicesHW = new VertexBuffer(Type.HWBoneIndex);
        indicesHW.setUsage(Usage.CpuOnly); // Setting usage to CpuOnly so that the buffer is not send empty to the GPU
        weightsHW.setUsage(Usage.CpuOnly);
        mesh.setBuffer(weightsHW);
        mesh.setBuffer(indicesHW);
        int bonesLimitExceeded = 0;
        // Accumulate skin bones influence into mesh buffers
        for(FbxNode limb : bones) {
            FbxCluster cluster = limb.skinToCluster.get(id);
            if(cluster == null || cluster.indexes == null || cluster.weights == null || cluster.indexes.length != cluster.weights.length)
                continue;
            if(limb.boneIndex > 255)
                throw new AssetLoadException("Bone index can't be packed into byte");
            for(int i = 0; i < cluster.indexes.length; ++i) {
                int vertexIndex = cluster.indexes[i];
                if(vertexIndex >= fbxMesh.reverseVertexMap.size())
                    throw new AssetLoadException("Invalid skinning vertex index. Unexpected index lookup " + vertexIndex + " from " + fbxMesh.reverseVertexMap.size());
                List dstVertices = fbxMesh.reverseVertexMap.get(vertexIndex);
                for(int j = 0; j < dstVertices.size(); ++j) {
                    int v = dstVertices.get(j);
                    // Append bone index and weight to vertex
                    int offset;
                    int smallestOffset = 0;
                    float w = 0;
                    float smallestW = Float.MAX_VALUE;
                    for(offset = v * 4; offset < v * 4 + 4; ++offset) {
                        w = boneWeightData.get(offset);
                        if(w == 0)
                            break;
                        if(w < smallestW) {
                            smallestW = w;
                            smallestOffset = offset;
                        }
                    }
                    if(w == 0) {
                        boneWeightData.put(offset, (float) cluster.weights[i]);
                        boneIndicesData.put(offset, (byte) limb.boneIndex);
                    } else {
                        if((float) cluster.weights[i] > smallestW) { // If current weight more than smallest, discard smallest
                            boneWeightData.put(smallestOffset, (float) cluster.weights[i]);
                            boneIndicesData.put(smallestOffset, (byte) limb.boneIndex);
                        }
                        bonesLimitExceeded++;
                    }
                }
            }
        }
        if(bonesLimitExceeded > 0)
            scene.warning("Skinning support max 4 bone per vertex. Exceeding data of " + bonesLimitExceeded + " weights in mesh bones will be discarded");
        // Postprocess bones weights
        int maxWeightsPerVert = 0;
        boneWeightData.rewind();
        for(int v = 0; v < fbxMesh.vCount; v++) {
            float w0 = boneWeightData.get();
            float w1 = boneWeightData.get();
            float w2 = boneWeightData.get();
            float w3 = boneWeightData.get();
            if(w3 != 0) {
                maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
            } else if(w2 != 0) {
                maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
            } else if(w1 != 0) {
                maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
            } else if(w0 != 0) {
                maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
            }
            float sum = w0 + w1 + w2 + w3;
            if(sum != 1f) {
                // normalize weights
                float mult = (sum != 0) ? (1f / sum) : 0;
                boneWeightData.position(v * 4);
                boneWeightData.put(w0 * mult);
                boneWeightData.put(w1 * mult);
                boneWeightData.put(w2 * mult);
                boneWeightData.put(w3 * mult);
            }
        }
        return maxWeightsPerVert;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy