/** Return true if we support high-quality avatars */
  public static boolean supportsHighQualityAvatars() {
    String shaderCheck = System.getProperty("avatar.shaderCheck");
    boolean shaderPass = true;

    // Check to see if the system supports OpenGL 2.0. If not, then
    // always use the low-detail avatar character
    RenderManager rm = ClientContextJME.getWorldManager().getRenderManager();
    if (shaderCheck != null && shaderCheck.equals("true")) {
      shaderPass = rm.getContextCaps().GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB >= 512;
    }

    // Issue 1114: make sure the system is telling the truth about what
    // it supports by trying a mock shader program
    boolean uniformsPass = shaderPass && ShaderTest.getInstance().testShaders();

    logger.warning(
        "Checking avatar detail level.  OpenGL20: "
            + rm.supportsOpenGL20()
            + " ShaderCheck: "
            + shaderPass
            + " UniformsCheck: "
            + uniformsPass);

    // OWL issue #110 -- ignore the value of supportsOpenGL20() here. This
    // is known to report false negatives on at least one graphics card.
    // Our shader test should do an adequate job determining whether a
    // graphics card supports the OpenGL 2.0 features we use.

    // Update: fixed version of supportsOpenGL20 should properly detect
    // version.

    return rm.supportsOpenGL20() && shaderPass && uniformsPass;
  }
  /**
   * Load and return the avatar. To make this the current avatar changeAvatar() must be called
   *
   * @param avatarConfigURL
   * @return
   */
  private WlAvatarCharacter loadAvatarInternal(AvatarConfigInfo avatarConfigInfo)
      throws MalformedURLException, IOException {

    WlAvatarCharacter ret = null;
    PMatrix origin = new PMatrix();
    CellTransform transform = cell.getLocalTransform();
    origin.setTranslation(transform.getTranslation(null));
    origin.setRotation(transform.getRotation(null));

    // Create the character
    String avatarDetail = System.getProperty("avatar.detail", "high");

    // check if we support high-quality avatars
    if (!supportsHighQualityAvatars()) {
      logger.warning("Forcing low detail.");
      avatarDetail = "low";
    }

    // Check to see if there is no avatar configuration info and/or if we
    // have the avatar details set to "low". If so, then use the default
    AvatarLoaderRegistry registry = AvatarLoaderRegistry.getAvatarLoaderRegistry();
    if (avatarConfigInfo == null || avatarDetail.equalsIgnoreCase("low")) {

      // Find the "default" factory to generate an avatar. Ask it to
      // loader the avatar. If it does not exist (it should), simply
      // log an error andr return.
      AvatarLoaderFactorySPI factory = registry.getDefaultAvatarLoaderFactory();
      if (factory == null) {
        logger.warning("No default avatar factory is registered.");
        return null;
      }

      // We need to rewrite the AvatarConfigInfo object here a bit,
      // otherwise, the loader may still loader the wrong avatar. If
      // we set the URL in the AvatarConfigInfo to null, that should do
      // the trick. (Note that since we manually obtained the
      // AvatarLoaderFactorySPI, we don't need to update the factory
      // class name in the AvatarConfigInfo object, but we do anyway).
      String defaultClassName = factory.getClass().getName();
      AvatarConfigInfo defaultInfo = new AvatarConfigInfo(null, defaultClassName);

      // Go ahead and load the default avatar
      ret = factory.getAvatarLoader().getAvatarCharacter(cell, username, defaultInfo);
    } else {
      // If the avatar has a non-null configuration information, then
      // ask the loader factory to generate a new loader for this avatar
      String className = avatarConfigInfo.getLoaderFactoryClassName();
      if (className == null) {
        logger.warning(
            "No class name given for avatar configuration"
                + " with url "
                + avatarConfigInfo.getAvatarConfigURL());
        return null;
      }

      // Find the avatar factory, if it does not exist, return an error
      AvatarLoaderFactorySPI factory = registry.getAvatarLoaderFactory(className);
      if (factory == null) {
        logger.warning(
            "No avatar loader factory for the class name "
                + className
                + " with url "
                + avatarConfigInfo.getAvatarConfigURL());
        return null;
      }

      // Ask the avatar loader to create and return an avatar character
      ret = factory.getAvatarLoader().getAvatarCharacter(cell, username, avatarConfigInfo);
    }

    ret.getModelInst().getTransform().getLocalMatrix(true).set(origin);

    // XXX NPC HACK XXX
    // TODO - remove hardcoded npc support
    //        if (username.equals("npc") && avatarConfigURL != null) {
    //            String u = avatarConfigURL.getFile();
    //            username = u.substring(u.lastIndexOf('/') + 1, u.lastIndexOf('.'));
    //        }

    // Sets the Z-buffer state on the external kids root
    Node external = ret.getJScene().getExternalKidsRoot();
    setZBufferState(external);

    // JSCENE HAS NOT CHILDREN, so this does nothing
    //        ret.getJScene().updateGeometricState(0, true);
    //        GraphicsUtils.printGraphBounds(ret.getJScene());

    //        JScene jscene = avatar.getJScene();
    //        jscene.renderToggle();      // both renderers
    //        jscene.renderToggle();      // jme renderer only
    //        jscene.setRenderPRendererMesh(true);  // Force pRenderer to be instantiated
    //        jscene.toggleRenderPRendererMesh();   // turn off mesh
    //        jscene.toggleRenderBoundingVolume();  // turn off bounds

    return ret;
  }