public static void readActor(BufferedReader actorFile, E3DActor actor) throws Exception {
    StringTokenizer tokenizer = null;
    String line = "";
    String str;

    while ((line = actorFile.readLine()) != null) {
      tokenizer = new StringTokenizer(line, TOKENIZER_CHARS);
      while (tokenizer.hasMoreTokens()) {
        str = readNextValue(tokenizer);

        if (ACTORTAG_TEXTURESET.equals(str)) {
          String path = tokenizer.nextToken();
          String jar = tokenizer.nextToken();
          actor.getWorld().loadTextureSet(path);
        } else if (ACTORTAG_MODEL.equals(str)) {
          if (!readActor(actorFile, tokenizer, actor))
            actor
                .getEngine()
                .getLogger()
                .writeLine(
                    E3DEngineLogger.SEVERITY_ERROR, "Unable to read actor model: " + actorFile);
        } else if (COMMENT.equalsIgnoreCase(str)) {
          break; // skip whole line
        }
        //                else
        //              	actor.getEngine().getLogger().writeLine(E3DEngineLogger.SEVERITY_INFO,
        // "Unknown tag in actor file: " + str);
      }
    }
  }
  private static void linkTrianglesToBones(
      E3DActor actor, HashMap orphanedTriangleBoneAttachments) {
    ArrayList triangleList = actor.getTriangleList();
    E3DTriangle triangle = null;
    for (int i = 0; i < triangleList.size(); i++) {
      triangle = (E3DTriangle) triangleList.get(i);

      if (orphanedTriangleBoneAttachments.containsKey(triangle)) {
        VertexBoneInformation boneInfo =
            (VertexBoneInformation) orphanedTriangleBoneAttachments.get(triangle);
        E3DBone bone;

        for (int b = 0; b < 3; b++) {
          bone = actor.getSkeleton().findBoneByID(boneInfo.boneIDs[b]);
          if (bone != null) bone.attachVertex(triangle.getVertex(b));
          else
            actor
                .getEngine()
                .getLogger()
                .writeLine(
                    E3DEngineLogger.SEVERITY_WARNING,
                    "The bone: "
                        + E3DStringHelper.getNonNullableString(boneInfo.boneIDs[b])
                        + " could not be found in the actor thus vertices were not linked to it.");
        }
      }
    }
  }
  private static void linkOrphanedBonesToNewBone(
      E3DActor actor, E3DBone possibleParentBone, E3DHashListMap orphanedBones) {
    if (orphanedBones.containsKey(possibleParentBone.getBoneID())) {
      ArrayList boneList = (ArrayList) orphanedBones.get(possibleParentBone.getBoneID());
      for (int i = 0; i < boneList.size(); i++) {
        E3DBone childBone = (E3DBone) boneList.get(i);

        actor.getSkeleton().addBone(childBone, possibleParentBone.getBoneID());

        // Try to recursively link other orphans to the newly attached bones
        linkOrphanedBonesToNewBone(actor, childBone, orphanedBones);
      }

      // We had to have added all of these, so remove them
      orphanedBones.remove(possibleParentBone.getBoneID());
    }
  }
  private static boolean readActor(
      BufferedReader actorFile, StringTokenizer tokenizer, E3DActor actor) throws IOException {
    String str;
    String line;
    boolean startBlockFound = false;
    boolean endBlockFound = false;

    // Hold onto bones that don't have parents yet
    // (String parentBoneID, (ArrayList)E3DBone bone)
    E3DHashListMap orphanedBones = new E3DHashListMap();

    // We can't attach triangles to bones until the bones are all loaded, so we do that in the end
    // (E3DTriangle triangle, VertexBoneInformation boneInformation)
    HashMap orphanedTriangleBoneAttachments = new HashMap();

    while (!endBlockFound && (line = actorFile.readLine()) != null) {
      tokenizer = new StringTokenizer(line, TOKENIZER_CHARS);
      while (!endBlockFound && tokenizer.hasMoreTokens()) {
        str = readNextValue(tokenizer);
        if (START_BLOCK.equals(str)) {
          startBlockFound = true;
        } else if (ACTORTAG_BONE.equals(str)) {
          if (!startBlockFound) return false;

          String boneID = readNextValue(tokenizer);
          String parentBoneID = readNextValue(tokenizer);

          E3DBone bone = readBone(actor.getEngine(), actorFile, boneID);
          if (bone != null) {
            if (!actor.getSkeleton().addBone(bone, parentBoneID))
              orphanedBones.put(parentBoneID, bone);
            else // did add it, so there may be some children of it
            { // by the time we get done adding the last bone, we should be able to link up all
              // children
              linkOrphanedBonesToNewBone(actor, bone, orphanedBones);
            }
          } else
            actor
                .getEngine()
                .getLogger()
                .writeLine(
                    E3DEngineLogger.SEVERITY_WARNING,
                    "Unable to read bone: "
                        + E3DStringHelper.getNonNullableString(boneID)
                        + ".  Possibly missing attributes or malformed definition.");
        } else if (ACTORTAG_TRIANGLE.equals(str)) {
          if (!startBlockFound) return false;

          TempTriangle tempTriangle = readTriangle(actor.getEngine(), actorFile);

          if (tempTriangle != null) {
            E3DTriangle triangle = tempTriangle.triangle;
            VertexBoneInformation boneInformation = tempTriangle.vertexBoneInformation;
            if (triangle != null) {
              actor.getMesh().addTriangle(triangle);
              if (boneInformation != null) // these will be linked later
              orphanedTriangleBoneAttachments.put(triangle, boneInformation);
            }
          }
        } else if (ACTORTAG_ANIMATION.equals(str)) {
          if (!startBlockFound) return false;

          String animationName = readNextValue(tokenizer);
          String fps = readNextValue(tokenizer);
          double dFPS = 20.0;
          try {
            dFPS = Double.parseDouble(fps);
          } catch (NumberFormatException e) {
            actor
                .getEngine()
                .getLogger()
                .writeLine(
                    E3DEngineLogger.SEVERITY_WARNING,
                    "No FPS specified for animation.  20 FPS will be used by default (1 frame == 0.5s)");
          }
          if (dFPS == 0) dFPS = 20.0;

          E3DAnimation animation = readAnimation(actor.getEngine(), animationName, dFPS, actorFile);
          if (animation != null) actor.getSkeleton().addAnimation(animation);

        } else if (COMMENT.equals(str)) break;
        else if (END_BLOCK.equals(str)) endBlockFound = true;
        else
          actor
              .getEngine()
              .getLogger()
              .writeLine(
                  E3DEngineLogger.SEVERITY_INFO, "Unknown tag in actor model definition: " + str);
      }
    }

    linkTrianglesToBones(actor, orphanedTriangleBoneAttachments);

    return endBlockFound;
  }