md2 models -> distorted textures

here's what i made:

http://img165.imageshack.us/my.php?image=wandqg9.jpg



and this is how it should look like:

http://archive.doomsdayhq.com/screenshots/jheretic/4new.jpg



and this is the code loading the model:

public static Spatial loadMD2(final String p_file, final String p_texture, final DisplaySystem p_display) throws IOException {
    final Md2ToJme l_conv = new Md2ToJme();
    return Utils.asJME(l_conv, p_file, getImage(p_texture), p_display);
  }

  private static Spatial asJME(final Md2ToJme p_conv, final String p_file, final Image p_image, final DisplaySystem p_display) throws IOException {
    final ByteArrayOutputStream l_baos = new ByteArrayOutputStream();
    p_conv.convert(new FileInputStream(getFile(p_file)), l_baos);
    final Node l_node = (Node) new BinaryImporter().load(l_baos.toByteArray());
    TextureState ts = p_display.getRenderer().createTextureState();
    ts.setEnabled(true);
    ts.setTexture(
       TextureManager.loadTexture(
          p_image,
          Texture.MM_LINEAR,
          Texture.FM_LINEAR,
          true));
    l_node.setRenderState(ts);
    KeyframeController kc = (KeyframeController) l_node.getChild(0).getController(0);
    kc.setSpeed(0.01f);
    final CullState cs = p_display.getRenderer().createCullState();
    cs.setCullMode(CullState.CS_FRONT);
    l_node.setRenderState(cs);
    return l_node;
  }

  public static File getFile(final String p_name) {
    final File l_url = new File("resources/" + p_name);
    if (!l_url.exists()) {
      throw new ThisShouldneverHappen(p_name + " not found");
    } else {
      System.out.println("File " + p_name + " accessed");
    }
    return l_url;
  }



the model & texture.
http://radix.hopto.org/Wand.zip

any idea what's wrong here?

converting the texture into a png didn't help.

i tested a few other models. some look good, some have strange textures. is there a bug in jmonkeyengine, or is it me?

the problem seems to be independent from animations. i tried loading a few static models, and half of them worked fine, the other half didn't.

come on, give me a hint. does the problem appear everywhere or is it my fault?

Instead of:



Texture.MM_LINEAR,

          Texture.FM_LINEAR,



Try:



Texture.MM_LINEAR_LINEAR,

          Texture.FM_LINEAR,



You also might try:



TextureManager.loadTexture(

          p_image,

          Texture.MM_LINEAR,

          Texture.FM_LINEAR, 1.0f, Image.GUESS_FORMAT_NO_S3TC,

          true));

i am currently unable to run any opengl-application. could you try to load the model at http://radix.hopto.org/Wand.zip and see how it looks like? i used jmonkey 0.11, maybe it's fixed in the night build?

So I've had a look.  It seems like there is something incorrect with the md2 loader.  I'll see if I can determine more later.

Ok, well, I've had a nice long look at the md2 loader and aside from not supporting extended opengl options like trangle fans / strips (which are optional because it can fall back to pure triangles) and not using the anorms.h predefined normals table (which I've now added), I don't see anything wrong. 



I'm confused on a couple of items.


  1. how color is being added to your model.  When I open the pcx file in photoshop, I see a gray scale image.
  2. what's up with the tex coords.  I can not see anything wrong with how they are read and used, and yet they are wrong.  :frowning:



    Any ideas?
  1. the image is colored. try opening it with irfanview and you'll see it.
  2. whatever the error is, it's not in the file. i tried several md2 loaders now, and all display the texture correctly.

    however, i found a model which is displayed perfectly and one that is displayed totally wrong. it's wrong in such a way that you might set a conditional breakpoint and stop right at the bugs' source.

    i uploaded these 2 models: http://radix.hopto.org/jHRP.rar



    the one which works is called Vial, the weird one Torch. if you look at the torch texture, you'll see that there are 2 separate animations, but if i load the model, disable the animations and show the first one, it seems the whole texture is wrapped around the model instead of half of it. the bug should be easier to find here.

anything new yet? if not, i'll start investigating myself in about 25 hours

anyone still interested? i think i got it. if i'm right, whoever wrote that bug is going to die of shame.

edit:

i was wrong :frowning: but i found something:

every model having the same number of texCoords and vertices is rendered correctly. except for drfreak, every model having a lower number of texcoords was rendered buggy.

investigating further



GOT IT!


//reorginize coordinates to match the vertex index.
                    if (numTexVertex != 0) {
                      for (int j = 0; j < numOfFaces; j++) {
                        int index = faces[j].vertIndex[0];
//                        texCoords2[index] = new Vector2f();   ??? Why was this here???
                        texCoords2[index] = texVerts[faces[j].coordIndex[0]];

                        index = faces[j].vertIndex[1];
//                        texCoords2[index] = new Vector2f();
                        texCoords2[index] = texVerts[faces[j].coordIndex[1]];

                        index = faces[j].vertIndex[2];
//                        texCoords2[index] = new Vector2f();   ??
                        texCoords2[index] = texVerts[faces[j].coordIndex[2]];
                      }
                    }



this code is evil, wrong, twisted, buggy, and even more evil. i needed hours to figure out why, so i'll fix it for myself and won't tell you^^
j/k

the code works if, and only if every vertice has one tex coord. this is wrong. one vertice might have 2 or 3, as every face of the model might have a part of the texture that is not connected to the texture of the faces near it.

i'll see if i can fix it.

i could fix it:
/**
        *
        * <code>convertDataStructures</code> takes the loaded MD2 data and
        * converts it into jME data.
        *
        */
        private void convertDataStructures() {
            triMesh = new TriMesh[header.numFrames];
            Vector2f[] texCoords2 = new Vector2f[header.numTriangles*3];
            controller = new KeyframeController();
            for (int i = 0; i < header.numFrames; i++) {
                int numOfVerts = header.numVertices;
                int numTexVertex = header.numTexCoords;
                int numOfFaces = header.numTriangles;
                Vector3f[] unwrappedVerts = new Vector3f[numOfFaces*3];
                if (i!=0)
                    triMesh = new TriMesh();
                else
                    triMesh = tm;
                Vector3f[] uniqueVerts = new Vector3f[numOfVerts];
                Vector2f[] texVerts = new Vector2f[numTexVertex];

                Face[] faces = new Face[numOfFaces];

                //assign a vector array for the trimesh.
                for (int j = 0; j < numOfVerts; j++) {
                    uniqueVerts[j] = new Vector3f();
                    uniqueVerts[j].x = frames.vertices[j].vertex.x;
                    uniqueVerts[j].y = frames.vertices[j].vertex.y;
                    uniqueVerts[j].z = frames.vertices[j].vertex.z;
                    if (i!=0)
                        uniqueVerts[j]=frames.vertices[j].vertex;
                    else
                        uniqueVerts[j]=new Vector3f(frames.vertices[j].vertex);
                }

                //set up the initial indices array.
                for (int j = 0; j < numOfFaces; j++) {
                    faces[j] = new Face();
                    faces[j].vertIndex[0] = triangles[j].vertexIndices[0];
                    faces[j].vertIndex[1] = triangles[j].vertexIndices[1];
                    faces[j].vertIndex[2] = triangles[j].vertexIndices[2];

                    faces[j].coordIndex[0] = triangles[j].textureIndices[0];
                    faces[j].coordIndex[1] = triangles[j].textureIndices[1];
                    faces[j].coordIndex[2] = triangles[j].textureIndices[2];
                }

                if (i == 0) {
                    //texture coordinates.
                    for (int j = 0; j < numTexVertex; j++) {
                        texVerts[j] = new Vector2f();
                        texVerts[j].x = texCoords[j].x / (header.skinWidth);
                        texVerts[j].y =
                            1 - texCoords[j].y / (header.skinHeight);
                    }

                    //reorginize coordinates to match the vertex index.
                    if (numTexVertex != 0) {
                      int l_count = 0;
                      for (int j = 0; j < numOfFaces; j++) {
                        unwrappedVerts[l_count] = uniqueVerts[faces[j].vertIndex[0]];
                        texCoords2[l_count++] = texVerts[faces[j].coordIndex[0]];
                        unwrappedVerts[l_count] = uniqueVerts[faces[j].vertIndex[1]];
                        texCoords2[l_count++] = texVerts[faces[j].coordIndex[1]];
                        unwrappedVerts[l_count] = uniqueVerts[faces[j].vertIndex[2]];
                        texCoords2[l_count++] = texVerts[faces[j].coordIndex[2]];
                      }
                    }

                    int[] dummyIndices = new int[numOfFaces * 3];
                    int count = 0;
                    for (int j = 0; j < numOfFaces*3; j++) {
                        dummyIndices[count] = count++;
                    }
                    triMesh.setIndexBuffer(0, BufferUtils.createIntBuffer(dummyIndices));
                    triMesh.setTextureBuffer(0, BufferUtils.createFloatBuffer(texCoords2));
                    controller.setMorphingMesh(triMesh);

                }  // End if (i==0)
                else {
                  int l_count = 0;
                  for (int j = 0; j < numOfFaces; j++) {
                    unwrappedVerts[l_count++] = uniqueVerts[faces[j].vertIndex[0]];
                    unwrappedVerts[l_count++] = uniqueVerts[faces[j].vertIndex[1]];
                    unwrappedVerts[l_count++] = uniqueVerts[faces[j].vertIndex[2]];
                  }
                }

                triMesh.setVertexBuffer(0, BufferUtils.createFloatBuffer(unwrappedVerts));
                triMesh.setNormalBuffer(0, BufferUtils.createFloatBuffer(computeNormals(faces, unwrappedVerts, uniqueVerts)));
                if (i!=0) controller.setKeyframe(i-1,triMesh);
            }
            //build controller. Attach everything.
//            this.attachChild(triMesh[0]);
//            triMesh[0].addController(controller);
            tm.addController(controller);
        }

        /**
        *
        * <code>computeNormals</code> calculates the normals of
        * the model.
        * @param faces the faces of the model.
        * @param allVerts the vertices of the model, unwrapped
        * @param uniqueVerts all unique vertices of the model.
        * @return the array of normals.
        */
        private Vector3f[] computeNormals(Face[] faces, Vector3f[] allVerts, final Vector3f[] uniqueVerts) {
            Vector3f[] returnNormals = new Vector3f[allVerts.length];

            Vector3f[] tempNormals = new Vector3f[faces.length];

            for (int i = 0; i < faces.length; i++) {
                tempNormals =
                  uniqueVerts[faces.vertIndex[0]].subtract(
                      uniqueVerts[faces.vertIndex[2]]).cross(
                      uniqueVerts[faces.vertIndex[2]].subtract(
                        uniqueVerts[faces.vertIndex[1]]));
            }

            Vector3f sum = new Vector3f();
            int shared = 0;

            for (int i = 0; i < uniqueVerts.length; i++) {
                for (int j = 0; j < faces.length; j++) {
                    if (faces[j].vertIndex[0] == i
                        || faces[j].vertIndex[1] == i
                        || faces[j].vertIndex[2] == i) {
                        sum.addLocal(tempNormals[j]);
                        shared++;
                    }
                }

                final Vector3f normal = sum.divide(-shared);
                normal.normalizeLocal().negateLocal();
                for (int j = 0; j < allVerts.length; j++) {
                  Vector3f l_allVert = allVerts[j];
                  if (l_allVert.equals(uniqueVerts))
                {
                    returnNormals[j] = normal;
                }
              }

              sum.zero();
                shared = 0;
            }

            return returnNormals;
        }


the code works, requires more memory, more time to convert the model, and more time to render it. but it works.
possible optimization:
currently, i all vertices are unwrapped. even if a vertice/texcoord-tuple is used multiple times, it is not accessed via indices. i think i cannot resist and will do it myself.

edit edit:
i'm done. there is no measureable difference in speed comapared to the buggy version anymore. however, the changes were a little too much to post them here. where should i put them?

Go ahead and send it my way and I'll integrate it.