Search in sources :

Example 1 with SkeletalMeshDataBuilder

use of org.terasology.rendering.assets.skeletalmesh.SkeletalMeshDataBuilder in project Terasology by MovingBlocks.

the class MD5SkeletonLoader method load.

@Override
public SkeletalMeshData load(ResourceUrn urn, List<AssetDataFile> inputs) throws IOException {
    try (InputStream stream = inputs.get(0).openStream()) {
        MD5 md5 = parse(stream);
        SkeletalMeshDataBuilder skeletonBuilder = new SkeletalMeshDataBuilder();
        List<Bone> bones = Lists.newArrayListWithCapacity(md5.numJoints);
        for (int i = 0; i < md5.numJoints; ++i) {
            MD5Joint joint = md5.joints[i];
            Bone bone = new Bone(i, joint.name, joint.position, joint.orientation);
            bones.add(bone);
            if (joint.parent != -1) {
                bones.get(joint.parent).addChild(bone);
            }
            skeletonBuilder.addBone(bone);
        }
        if (md5.meshes.length > 0) {
            // TODO: Support multiple mesh somehow?
            MD5Mesh mesh = md5.meshes[0];
            for (MD5Weight weight : mesh.weightList) {
                skeletonBuilder.addWeight(new BoneWeight(weight.position, weight.bias, weight.jointIndex));
            }
            List<Vector2f> uvs = Lists.newArrayList();
            TIntList vertexStartWeight = new TIntArrayList(mesh.numVertices);
            TIntList vertexWeightCount = new TIntArrayList(mesh.numVertices);
            for (MD5Vertex vert : mesh.vertexList) {
                uvs.add(vert.uv);
                vertexStartWeight.add(vert.startWeight);
                vertexWeightCount.add(vert.countWeight);
            }
            skeletonBuilder.setVertexWeights(vertexStartWeight, vertexWeightCount);
            skeletonBuilder.setUvs(uvs);
            TIntList indices = new TIntArrayList(mesh.indexList.length);
            for (int i = 0; i < mesh.numTriangles; ++i) {
                indices.add(mesh.indexList[i * 3]);
                indices.add(mesh.indexList[i * 3 + 2]);
                indices.add(mesh.indexList[i * 3 + 1]);
            }
            skeletonBuilder.setIndices(indices);
        }
        return skeletonBuilder.build();
    } catch (NumberFormatException e) {
        throw new IOException("Error parsing " + inputs.get(0).getFilename(), e);
    }
}
Also used : InputStream(java.io.InputStream) IOException(java.io.IOException) TIntArrayList(gnu.trove.list.array.TIntArrayList) BoneWeight(org.terasology.rendering.assets.skeletalmesh.BoneWeight) SkeletalMeshDataBuilder(org.terasology.rendering.assets.skeletalmesh.SkeletalMeshDataBuilder) Vector2f(org.terasology.math.geom.Vector2f) Bone(org.terasology.rendering.assets.skeletalmesh.Bone) TIntList(gnu.trove.list.TIntList)

Example 2 with SkeletalMeshDataBuilder

use of org.terasology.rendering.assets.skeletalmesh.SkeletalMeshDataBuilder in project Terasology by MovingBlocks.

the class ColladaLoader method parseSkeletalMeshData.

protected void parseSkeletalMeshData(Element rootElement) throws ColladaParseException {
    List<MD5Joint> md5JointList = new ArrayList<>();
    List<MD5Mesh> md5MeshList = new ArrayList<>();
    skeletonBuilder = new SkeletalMeshDataBuilder();
    // TODO: we need a better way to construct the parent/child nodes, especially for the non-joint nodes
    // MAYBE we can construct all of the nodes up-front, and then fill in the missing data for the ones of type JOINT later
    // And only keep the MD5 nodes in the final list if they are used?
    Map<String, MD5Joint> md5JointBySidMap = new HashMap<>();
    MD5Joint parentMD5Joint = null;
    ElementSet nodeParentSet = rootElement.find("library_visual_scenes", "visual_scene", "node");
    for (Element element : nodeParentSet) {
        createMd5JointForElementAndParent(md5JointBySidMap, element, parentMD5Joint);
    }
    ElementSet controllerSet = rootElement.find("library_controllers", "controller");
    for (Element controller : controllerSet) {
        ElementSet skinSet = controller.find("skin");
        if (1 != skinSet.size()) {
            throw new ColladaParseException("Found " + skinSet.size() + " skin sets for controller id=" + controller.id() + " name=" + controller.name());
        }
        Element skin = skinSet.first();
        ElementSet jointsSet = skin.find("joints");
        if (1 != jointsSet.size()) {
            throw new ColladaParseException("Found " + jointsSet.size() + " joints sets for controller id=" + controller.id() + " name=" + controller.name());
        }
        Element joints = jointsSet.first();
        ElementSet vertexWeightsSet = skin.find("vertex_weights");
        if (1 != vertexWeightsSet.size()) {
            throw new ColladaParseException("Found " + vertexWeightsSet.size() + " vertex weights sets for controller id=" + controller.id() + " name=" + controller.name());
        }
        Element vertexWeights = vertexWeightsSet.first();
        String vertexWeightsCountString = vertexWeights.attr("count");
        int vertexWeightsCount = Integer.parseInt(vertexWeightsCountString);
        String[] jointNameArray = null;
        float[] inverseBindMatrixArray;
        Quat4f[] rotationArray;
        ElementSet jointsInputSet = joints.find("input");
        List<Input> inputList = parseInputs(jointsInputSet);
        for (Input jointsInput : inputList) {
            if ("JOINT".equals(jointsInput.semantic)) {
                Element jointNameSourceElement = skin.select(jointsInput.sourceName);
                Source jointNameSource = parseSource(jointNameSourceElement);
                jointNameArray = jointNameSource.nameValues;
            }
            if ("INV_BIND_MATRIX".equals(jointsInput.semantic)) {
                Element jointMatrixSourceElement = skin.select(jointsInput.sourceName);
                Source jointMatrixSource = parseSource(jointMatrixSourceElement);
                inverseBindMatrixArray = jointMatrixSource.floatValues;
                rotationArray = quad4fArrayFromFloat16ArrayData(inverseBindMatrixArray);
            }
        }
        List<MD5Weight> md5WeightList = Lists.newArrayList();
        float[] weightsArray = null;
        ElementSet vertexWeightsInputSet = vertexWeights.find("input");
        List<Input> vertexWeightsInputList = parseInputs(vertexWeightsInputSet);
        // TODO: for now, assume the offsets will always perfectly match the sorted-by-offset list indexes
        Collections.sort(vertexWeightsInputList, (i1, i2) -> i1.offset - i2.offset);
        for (int i = 0; i < vertexWeightsInputList.size(); i++) {
            Input input = vertexWeightsInputList.get(i);
            if (input.offset != i) {
                throw new ColladaParseException("vertex weights input list offset does not match list index for vertex weights input " + input + " for controller id=" + controller.id() + " name=" + controller.name());
            }
        }
        for (Input vertexWeightsInput : vertexWeightsInputList) {
            // }
            if ("WEIGHT".equals(vertexWeightsInput.semantic)) {
                Element jointMatrixSourceElement = skin.select(vertexWeightsInput.sourceName);
                Source weightsArraySource = parseSource(jointMatrixSourceElement);
                weightsArray = weightsArraySource.floatValues;
            }
        }
        ElementSet vertexWeightsVCountDataSet = vertexWeights.find("vcount");
        if (1 != vertexWeightsVCountDataSet.size()) {
            throw new ColladaParseException("Found " + vertexWeightsVCountDataSet.size() + " vertex weights vcount sets for controller id=" + controller.id() + " name=" + controller.name());
        }
        Element vertexWeightsVCountData = vertexWeightsVCountDataSet.first();
        String vertexWeightsVCountString = vertexWeightsVCountData.text();
        String[] vertexWeightsVCountStrings = getItemsInString(vertexWeightsVCountString);
        if (vertexWeightsVCountStrings.length != vertexWeightsCount) {
            throw new ColladaParseException("Expected " + vertexWeightsCount + " but was " + vertexWeightsVCountStrings.length + " for controller id=" + controller.id() + " name=" + controller.name());
        }
        ElementSet vertexWeightsVDataSet = vertexWeights.find("v");
        if (1 != vertexWeightsVDataSet.size()) {
            throw new ColladaParseException("Found " + vertexWeightsVDataSet.size() + " vertex weights v sets for controller id=" + controller.id() + " name=" + controller.name());
        }
        Element vertexWeightsVData = vertexWeightsVDataSet.first();
        String vertexWeightsVDataString = vertexWeightsVData.text();
        String[] vertexWeightsVStrings = getItemsInString(vertexWeightsVDataString);
        // if (vertexWeightsVStrings.length != (vertexWeightsCount * vertexWeightsInputList.size())) {
        // throw new ColladaParseException("Expected " + vertexWeightsCount + " * input count of "
        // + vertexWeightsInputList.size() + " but was "
        // + vertexWeightsVStrings.length + " for controller id=" + controller.id() + " name=" + controller.name());
        // }
        // TODO: these aren't actually needed once we are populating MD5Weight records
        String[] vertexWeightsJointNameArray = new String[vertexWeightsCount];
        float[] vertexWeightsArray = new float[vertexWeightsCount];
        int vertexWeightsVDataIndex = -1;
        for (int vertexWeightsIndex = 0; vertexWeightsIndex < vertexWeightsCount; vertexWeightsIndex++) {
            MD5Weight md5Weight = new MD5Weight();
            Vector3f vertexPosition = new Vector3f();
            vertexPosition.x = vertices.get(3 * vertexWeightsIndex + 0);
            vertexPosition.y = vertices.get(3 * vertexWeightsIndex + 1);
            vertexPosition.z = vertices.get(3 * vertexWeightsIndex + 2);
            md5Weight.position = vertexPosition;
            md5WeightList.add(md5Weight);
            String vCountString = vertexWeightsVCountStrings[vertexWeightsIndex];
            int vCount = Integer.parseInt(vCountString);
            for (int vCountIndex = 0; vCountIndex < vCount; vCountIndex++) {
                for (Input vertexWeightsInput : vertexWeightsInputList) {
                    // vCount varies each time
                    ++vertexWeightsVDataIndex;
                    String indexString = vertexWeightsVStrings[vertexWeightsVDataIndex];
                    int index = Integer.parseInt(indexString);
                    if (-1 == index) {
                        throw new ColladaParseException("We do not support indexing into the bind shape yet");
                    }
                    if ("JOINT".equals(vertexWeightsInput.semantic)) {
                        md5Weight.jointIndex = index;
                        vertexWeightsJointNameArray[vertexWeightsIndex] = jointNameArray[index];
                    // logger.debug(String.valueOf(vertexWeightsVDataIndex) + ": " + "jointName=" + vertexWeightsJointNameArray[vertexWeightsIndex]);
                    } else if ("WEIGHT".equals(vertexWeightsInput.semantic)) {
                        md5Weight.bias = weightsArray[index];
                        vertexWeightsArray[vertexWeightsIndex] = weightsArray[index];
                    // logger.debug(String.valueOf(vertexWeightsVDataIndex) + ": " + "weight=" + vertexWeightsArray[vertexWeightsIndex]);
                    } else {
                        throw new ColladaParseException("Found unexpected vertex weights Input semantic " + vertexWeightsInput.semantic + " for controller id=" + controller.id() + " name=" + controller.name());
                    }
                }
            }
        }
        MD5Mesh md5Mesh = new MD5Mesh();
        md5Mesh.weightList = md5WeightList;
        // Find a node with sid="joint-name"
        for (String jointName : jointNameArray) {
            MD5Joint md5Joint = md5JointBySidMap.get(jointName);
            if (null == md5Joint) {
                throw new ColladaParseException("Cannot find joint node for node sid value for joint " + jointName + " in nodes for library_visual_scenes");
            }
            md5JointList.add(md5Joint);
        }
    }
    Deque<MD5Joint> jointsToProcess = new LinkedList<>(md5JointList);
    while (!jointsToProcess.isEmpty()) {
        MD5Joint joint = jointsToProcess.pop();
        MD5Joint parentJoint = joint.parent;
        if (null != parentJoint) {
            if (!md5JointList.contains(parentJoint)) {
                md5JointList.add(parentJoint);
                jointsToProcess.push(parentJoint);
            }
        }
    }
    for (MD5Joint joint : md5JointList) {
        if (null == joint.position) {
            throw new ColladaParseException("no joint position for joint with element id " + joint.element.id());
        }
        if (null == joint.orientation) {
            throw new ColladaParseException("no joint orientation for joint with element id " + joint.element.id());
        }
        // index argument is not used for anything currently, so we'll just set it to -1
        joint.bone = new Bone(-1, joint.name, joint.position, joint.orientation);
    }
    for (MD5Joint joint : md5JointList) {
        // We can probably skip unused end nodes
        joint.childList.stream().filter(childJoint -> childJoint.bone != null).forEach(childJoint -> joint.bone.addChild(childJoint.bone));
    }
    for (MD5Joint joint : md5JointList) {
        skeletonBuilder.addBone(joint.bone);
    }
    if (md5MeshList.size() > 0) {
        // TODO: Support multiple mesh somehow?
        MD5Mesh mesh = md5MeshList.get(0);
        for (MD5Weight weight : mesh.weightList) {
            skeletonBuilder.addWeight(new BoneWeight(weight.position, weight.bias, weight.jointIndex));
        }
        List<Vector2f> uvs = Lists.newArrayList();
        TIntList vertexStartWeight = new TIntArrayList(vertices.size() / 3);
        TIntList vertexWeightCount = new TIntArrayList(vertices.size() / 3);
        for (int i = 0; i < vertices.size() / 3; i++) {
            vertexStartWeight.add(i);
            vertexWeightCount.add(1);
        }
        skeletonBuilder.setVertexWeights(vertexStartWeight, vertexWeightCount);
        for (int i = 0; i < normals.size() / 2; i++) {
            uvs.add(new Vector2f(normals.get(i * 2 + 0), normals.get(i * 2 + 1)));
        }
        skeletonBuilder.setUvs(uvs);
        skeletonBuilder.setIndices(indices);
    }
// Now if you have come this far, you should be able to read the geometry data,
// as well as the skeleton and skinning data from COLLADA documents. And you should be able to draw
// the model in raw triangles, as well as draw the skeleton. Although I haven't discussed how you
// can accumulate the world matrices for each joint and then draw in world coordinates for debugging
// purposes but I think I gave a hint that we have to multiply parent joint's world matrix with current
// joint's Joint matrix and save the result in current joint's world matrix. We have to start this
// process from the root bone. So that we don't have dirty world matrices from parents, and the root
// Joint's world matrix becomes the Joint matrix, since root don't have any parent. If you are also
// reading the COLLADA specification version 1.5 you can find the skinning equation so you should also
// be able to put the model in bind shape. How can we animate this model is still not covered and will
// be covered in the following sections.
// THIS IS THE TARGET GOAL:
/*
        Bones
        - String name
        - int index
        - V3 object position
        - Quat4f obj rotation
        - parent / children bones

        SkeletalMesh


        // This part may not be required if we can implement SkeletalMeshData methods without it

        //////////////

        public SkeletalMeshData(List<Bone> bones, List<BoneWeight> weights,
           List<Vector2f> uvs,
           TIntList vertexStartWeights, TIntList vertexWeightCounts,
           TIntList indices) {

        BoneWeight
        Vector3f position = new Vector3f();
        float bias;
        int boneIndex;
        Vector3f normal = new Vector3f();

        //////////////


           public Collection<Bone> getBones();
           public Bone getRootBone();
           public Bone getBone(String name);

           public int getVertexCount();

           public List<Vector3f> getBindPoseVertexPositions();
           public List<Vector3f> getVertexPositions(List<Vector3f> bonePositions, List<Quat4f> boneRotations);

           public List<Vector3f> getBindPoseVertexNormals();
           public List<Vector3f> getVertexNormals(List<Vector3f> bonePositions, List<Quat4f> boneRotations);

           public TIntList getIndices();
           public List<Vector2f> getUVs();
         */
}
Also used : Bone(org.terasology.rendering.assets.skeletalmesh.Bone) Arrays(java.util.Arrays) BoneWeight(org.terasology.rendering.assets.skeletalmesh.BoneWeight) TIntArrayList(gnu.trove.list.array.TIntArrayList) Element(org.eaxy.Element) LoggerFactory(org.slf4j.LoggerFactory) Vector3f(org.terasology.math.geom.Vector3f) SkeletalMeshDataBuilder(org.terasology.rendering.assets.skeletalmesh.SkeletalMeshDataBuilder) HashMap(java.util.HashMap) Deque(java.util.Deque) Document(org.eaxy.Document) Matrix4f(org.terasology.math.geom.Matrix4f) ArrayList(java.util.ArrayList) ElementSet(org.eaxy.ElementSet) Lists(com.google.common.collect.Lists) Map(java.util.Map) TFloatList(gnu.trove.list.TFloatList) LinkedList(java.util.LinkedList) TIntList(gnu.trove.list.TIntList) TFloatArrayList(gnu.trove.list.array.TFloatArrayList) Logger(org.slf4j.Logger) SkeletalMeshData(org.terasology.rendering.assets.skeletalmesh.SkeletalMeshData) NonMatchingPathException(org.eaxy.NonMatchingPathException) Vector2f(org.terasology.math.geom.Vector2f) IOException(java.io.IOException) List(java.util.List) Quat4f(org.terasology.math.geom.Quat4f) Collections(java.util.Collections) Xml(org.eaxy.Xml) InputStream(java.io.InputStream) ElementSet(org.eaxy.ElementSet) HashMap(java.util.HashMap) Element(org.eaxy.Element) TIntArrayList(gnu.trove.list.array.TIntArrayList) ArrayList(java.util.ArrayList) TFloatArrayList(gnu.trove.list.array.TFloatArrayList) BoneWeight(org.terasology.rendering.assets.skeletalmesh.BoneWeight) SkeletalMeshDataBuilder(org.terasology.rendering.assets.skeletalmesh.SkeletalMeshDataBuilder) Vector2f(org.terasology.math.geom.Vector2f) LinkedList(java.util.LinkedList) TIntArrayList(gnu.trove.list.array.TIntArrayList) Vector3f(org.terasology.math.geom.Vector3f) Bone(org.terasology.rendering.assets.skeletalmesh.Bone) TIntList(gnu.trove.list.TIntList) Quat4f(org.terasology.math.geom.Quat4f)

Aggregations

TIntList (gnu.trove.list.TIntList)2 TIntArrayList (gnu.trove.list.array.TIntArrayList)2 IOException (java.io.IOException)2 InputStream (java.io.InputStream)2 Vector2f (org.terasology.math.geom.Vector2f)2 Bone (org.terasology.rendering.assets.skeletalmesh.Bone)2 BoneWeight (org.terasology.rendering.assets.skeletalmesh.BoneWeight)2 SkeletalMeshDataBuilder (org.terasology.rendering.assets.skeletalmesh.SkeletalMeshDataBuilder)2 Lists (com.google.common.collect.Lists)1 TFloatList (gnu.trove.list.TFloatList)1 TFloatArrayList (gnu.trove.list.array.TFloatArrayList)1 ArrayList (java.util.ArrayList)1 Arrays (java.util.Arrays)1 Collections (java.util.Collections)1 Deque (java.util.Deque)1 HashMap (java.util.HashMap)1 LinkedList (java.util.LinkedList)1 List (java.util.List)1 Map (java.util.Map)1 Document (org.eaxy.Document)1