public void create() {
    Thread.setDefaultUncaughtExceptionHandler(
        new UncaughtExceptionHandler() {
          public void uncaughtException(Thread thread, Throwable ex) {
            ex.printStackTrace();
            Runtime.getRuntime().halt(0); // Prevent Swing from keeping JVM alive.
          }
        });

    prefs = Gdx.app.getPreferences("spine-skeletonviewer");
    ui = new UI();
    batch = new PolygonSpriteBatch();
    renderer = new SkeletonMeshRenderer();
    debugRenderer = new SkeletonRendererDebug();
    skeletonX = (int) (ui.window.getWidth() + (Gdx.graphics.getWidth() - ui.window.getWidth()) / 2);
    skeletonY = Gdx.graphics.getHeight() / 4;
    ui.loadPrefs();

    loadSkeleton(
        Gdx.files.internal(
            Gdx.app
                .getPreferences("spine-skeletonviewer")
                .getString("lastFile", "spineboy/spineboy.json")));

    ui.loadPrefs();
  }
  public void render() {
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    float delta = Gdx.graphics.getDeltaTime();

    // Draw skeleton origin lines.
    ShapeRenderer shapes = debugRenderer.getShapeRenderer();
    if (state != null) {
      shapes.setColor(Color.DARK_GRAY);
      shapes.begin(ShapeType.Line);
      shapes.line(skeleton.x, -99999, skeleton.x, 99999);
      shapes.line(-99999, skeleton.y, 99999, skeleton.y);
      shapes.end();
    }

    if (skeleton != null) {
      // Reload if skeleton file was modified.
      if (reloadTimer <= 0) {
        lastModifiedCheck -= delta;
        if (lastModifiedCheck < 0) {
          lastModifiedCheck = checkModifiedInterval;
          long time = skeletonFile.lastModified();
          if (time != 0 && lastModified != time) reloadTimer = reloadDelay;
        }
      } else {
        reloadTimer -= delta;
        if (reloadTimer <= 0) {
          loadSkeleton(skeletonFile);
          ui.toast("Reloaded.");
        }
      }

      // Pose and render skeleton.
      state.getData().setDefaultMix(ui.mixSlider.getValue());
      renderer.setPremultipliedAlpha(ui.premultipliedCheckbox.isChecked());

      skeleton.setFlip(ui.flipXCheckbox.isChecked(), ui.flipYCheckbox.isChecked());
      skeleton.setPosition(skeletonX, skeletonY);

      delta = Math.min(delta, 0.032f) * ui.speedSlider.getValue();
      skeleton.update(delta);
      state.update(delta);
      state.apply(skeleton);
      skeleton.updateWorldTransform();

      batch.setColor(Color.WHITE);
      batch.begin();
      renderer.draw(batch, skeleton);
      batch.end();

      debugRenderer.setBones(ui.debugBonesCheckbox.isChecked());
      debugRenderer.setRegionAttachments(ui.debugRegionsCheckbox.isChecked());
      debugRenderer.setBoundingBoxes(ui.debugBoundingBoxesCheckbox.isChecked());
      debugRenderer.setMeshHull(ui.debugMeshHullCheckbox.isChecked());
      debugRenderer.setMeshTriangles(ui.debugMeshTrianglesCheckbox.isChecked());
      debugRenderer.setPaths(ui.debugPathsCheckbox.isChecked());
      debugRenderer.draw(skeleton);
    }

    if (state != null) {
      // AnimationState status.
      status.setLength(0);
      for (int i = 0, n = state.getTracks().size; i < n; i++) {
        TrackEntry entry = state.getTracks().get(i);
        if (entry == null) continue;
        status.append(i);
        status.append(": [LIGHT_GRAY]");
        status(entry);
        status.append("[WHITE]");
        status.append(entry.animation.name);
        status.append('\n');
      }
      ui.statusLabel.setText(status);
    }

    // Render UI.
    ui.render();

    // Draw indicator lines for animation and mix times.
    if (state != null) {
      TrackEntry entry = state.getCurrent(0);
      if (entry != null) {
        shapes.begin(ShapeType.Line);

        float percent = entry.getAnimationTime() / entry.getAnimationEnd();
        float x = ui.window.getRight() + (Gdx.graphics.getWidth() - ui.window.getRight()) * percent;
        shapes.setColor(Color.CYAN);
        shapes.line(x, 0, x, 12);

        percent =
            entry.getMixDuration() == 0
                ? 1
                : Math.min(1, entry.getMixTime() / entry.getMixDuration());
        x = ui.window.getRight() + (Gdx.graphics.getWidth() - ui.window.getRight()) * percent;
        shapes.setColor(Color.RED);
        shapes.line(x, 0, x, 12);

        shapes.end();
      }
    }
  }
  void loadSkeleton(final FileHandle skeletonFile) {
    if (skeletonFile == null) return;

    try {
      // Setup a texture atlas that uses a white image for images not found in the atlas.
      Pixmap pixmap = new Pixmap(32, 32, Format.RGBA8888);
      pixmap.setColor(new Color(1, 1, 1, 0.33f));
      pixmap.fill();
      final AtlasRegion fake = new AtlasRegion(new Texture(pixmap), 0, 0, 32, 32);
      pixmap.dispose();

      String atlasFileName = skeletonFile.nameWithoutExtension();
      if (atlasFileName.endsWith(".json"))
        atlasFileName = new FileHandle(atlasFileName).nameWithoutExtension();
      FileHandle atlasFile = skeletonFile.sibling(atlasFileName + ".atlas");
      if (!atlasFile.exists()) atlasFile = skeletonFile.sibling(atlasFileName + ".atlas.txt");
      TextureAtlasData data =
          !atlasFile.exists() ? null : new TextureAtlasData(atlasFile, atlasFile.parent(), false);
      TextureAtlas atlas =
          new TextureAtlas(data) {
            public AtlasRegion findRegion(String name) {
              AtlasRegion region = super.findRegion(name);
              if (region == null) {
                // Look for separate image file.
                FileHandle file = skeletonFile.sibling(name + ".png");
                if (file.exists()) {
                  Texture texture = new Texture(file);
                  texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
                  region = new AtlasRegion(texture, 0, 0, texture.getWidth(), texture.getHeight());
                  region.name = name;
                }
              }
              return region != null ? region : fake;
            }
          };

      // Load skeleton data.
      String extension = skeletonFile.extension();
      if (extension.equalsIgnoreCase("json") || extension.equalsIgnoreCase("txt")) {
        SkeletonJson json = new SkeletonJson(atlas);
        json.setScale(ui.scaleSlider.getValue());
        skeletonData = json.readSkeletonData(skeletonFile);
      } else {
        SkeletonBinary binary = new SkeletonBinary(atlas);
        binary.setScale(ui.scaleSlider.getValue());
        skeletonData = binary.readSkeletonData(skeletonFile);
        if (skeletonData.getBones().size == 0) throw new Exception("No bones in skeleton data.");
      }
    } catch (Exception ex) {
      ex.printStackTrace();
      ui.toast("Error loading skeleton: " + skeletonFile.name());
      lastModifiedCheck = 5;
      return;
    }

    skeleton = new Skeleton(skeletonData);
    skeleton.setToSetupPose();
    skeleton = new Skeleton(skeleton); // Tests copy constructors.
    skeleton.updateWorldTransform();

    state = new AnimationState(new AnimationStateData(skeletonData));
    state.addListener(
        new AnimationStateAdapter() {
          public void event(TrackEntry entry, Event event) {
            ui.toast(event.getData().getName());
          }
        });

    this.skeletonFile = skeletonFile;
    prefs.putString("lastFile", skeletonFile.path());
    prefs.flush();
    lastModified = skeletonFile.lastModified();
    lastModifiedCheck = checkModifiedInterval;

    // Populate UI.

    ui.window.getTitleLabel().setText(skeletonFile.name());
    {
      Array<String> items = new Array();
      for (Skin skin : skeletonData.getSkins()) items.add(skin.getName());
      ui.skinList.setItems(items);
    }
    {
      Array<String> items = new Array();
      for (Animation animation : skeletonData.getAnimations()) items.add(animation.getName());
      ui.animationList.setItems(items);
    }
    ui.trackButtons.getButtons().first().setChecked(true);

    // Configure skeleton from UI.

    if (ui.skinList.getSelected() != null) skeleton.setSkin(ui.skinList.getSelected());
    setAnimation();

    // ui.animationList.clearListeners();
    // state.setAnimation(0, "walk", true);
  }