private void highlightEdges(final Collection<DefaultWeightedEdge> edges) {
    // Restore previous display settings for previously highlighted edges
    if (null != previousEdgeHighlight)
      for (final DefaultWeightedEdge edge : previousEdgeHighlight.keySet())
        trackNode.setColor(edge, previousEdgeHighlight.get(edge));

    // Store current color settings
    previousEdgeHighlight = new HashMap<DefaultWeightedEdge, Color>();
    for (final DefaultWeightedEdge edge : edges)
      previousEdgeHighlight.put(edge, trackNode.getColor(edge));

    // Change edge color
    final Color highlightColor = (Color) displaySettings.get(KEY_HIGHLIGHT_COLOR);
    for (final DefaultWeightedEdge edge : edges) trackNode.setColor(edge, highlightColor);
  }
  @Override
  public void render() {
    if (DEBUG) System.out.println("[SpotDisplayer3D] Call to render().");

    updateRadiuses();
    updateSpotColors();
    spotContent.setVisible((Boolean) displaySettings.get(KEY_SPOTS_VISIBLE));
    if (null != trackContent) {
      trackContent.setVisible((Boolean) displaySettings.get(KEY_TRACKS_VISIBLE));
      trackNode.setTrackDisplayMode((Integer) displaySettings.get(KEY_TRACK_DISPLAY_MODE));
      trackNode.setTrackDisplayDepth((Integer) displaySettings.get(KEY_TRACK_DISPLAY_DEPTH));
      updateTrackColors();
      trackNode.refresh();
      universe.updateStartAndEndTime(blobs.firstKey(), blobs.lastKey());
      universe.updateTimelineGUI();
    }
  }
  private void updateTrackColors() {
    final TrackColorGenerator colorGenerator =
        (TrackColorGenerator) displaySettings.get(KEY_TRACK_COLORING);

    for (final Integer trackID : model.getTrackModel().trackIDs(true)) {
      colorGenerator.setCurrentTrackID(trackID);
      for (final DefaultWeightedEdge edge : model.getTrackModel().trackEdges(trackID)) {
        final Color color = colorGenerator.color(edge);
        trackNode.setColor(edge, color);
      }
    }
  }
 @Override
 public void setDisplaySettings(final String key, final Object value) {
   super.setDisplaySettings(key, value);
   // Treat change of radius
   if (key == KEY_SPOT_RADIUS_RATIO) {
     updateRadiuses();
   } else if (key == KEY_SPOT_COLORING) {
     updateSpotColors();
   } else if (key == KEY_TRACK_COLORING) {
     updateTrackColors();
   } else if (key == KEY_DISPLAY_SPOT_NAMES) {
     for (final int frame : blobs.keySet()) {
       blobs.get(frame).setShowLabels((Boolean) value);
     }
   } else if (key == KEY_SPOTS_VISIBLE) {
     spotContent.setVisible((Boolean) value);
   } else if (key == KEY_TRACKS_VISIBLE && null != trackContent) {
     trackContent.setVisible((Boolean) value);
   } else if (key == KEY_TRACK_DISPLAY_MODE && null != trackNode) {
     trackNode.setTrackDisplayMode((Integer) value);
   } else if (key == KEY_TRACK_DISPLAY_DEPTH && null != trackNode) {
     trackNode.setTrackDisplayDepth((Integer) value);
   }
 }
  @Override
  public void modelChanged(final ModelChangeEvent event) {
    if (DEBUG) {
      System.out.println(
          "[SpotDisplayer3D: modelChanged() called with event ID: " + event.getEventID());
      System.out.println(event);
    }

    switch (event.getEventID()) {
      case ModelChangeEvent.SPOTS_COMPUTED:
        makeSpotContent();
        break;

      case ModelChangeEvent.SPOTS_FILTERED:
        for (final int frame : blobs.keySet()) {
          final SpotGroupNode<Spot> frameBlobs = blobs.get(frame);
          for (final Iterator<Spot> it = model.getSpots().iterator(frame, false); it.hasNext(); ) {
            final Spot spot = it.next();
            final boolean visible =
                spot.getFeature(SpotCollection.VISIBLITY).compareTo(SpotCollection.ZERO) > 0;
            frameBlobs.setVisible(spot, visible);
          }
        }
        break;

      case ModelChangeEvent.TRACKS_COMPUTED:
        trackContent = makeTrackContent();
        universe.removeContent(TRACK_CONTENT_NAME);
        universe.addContent(trackContent);
        break;

      case ModelChangeEvent.TRACKS_VISIBILITY_CHANGED:
        updateTrackColors();
        trackNode.setTrackVisible(model.getTrackModel().trackIDs(true));
        break;

      case ModelChangeEvent.MODEL_MODIFIED:
        {

          /*
           * Deal with spots first.
           */

          // Useful fields.
          @SuppressWarnings("unchecked")
          final FeatureColorGenerator<Spot> spotColorGenerator =
              (FeatureColorGenerator<Spot>) displaySettings.get(KEY_SPOT_COLORING);
          final double radiusRatio = (Double) displaySettings.get(KEY_SPOT_RADIUS_RATIO);

          // Iterate each spot of the event.
          final Set<Spot> spotsModified = event.getSpots();
          for (final Spot spot : spotsModified) {
            final int spotFlag = event.getSpotFlag(spot);
            final int frame = spot.getFeature(Spot.FRAME).intValue();
            final SpotGroupNode<Spot> spotGroupNode = blobs.get(frame);

            switch (spotFlag) {
              case ModelChangeEvent.FLAG_SPOT_REMOVED:
                spotGroupNode.remove(spot);
                break;

              case ModelChangeEvent.FLAG_SPOT_ADDED:
                {

                  // Sphere location and radius
                  final double[] coords = new double[3];
                  TMUtils.localize(spot, coords);
                  final Double radius = spot.getFeature(Spot.RADIUS);
                  final double[] pos =
                      new double[] {coords[0], coords[1], coords[2], radius * radiusRatio};
                  final Point4d center = new Point4d(pos);

                  // Sphere color
                  final Color4f color = new Color4f(spotColorGenerator.color(spot));
                  color.w = 0;

                  // Do we have an empty frame?
                  if (null == spotGroupNode) {
                    /*
                     * We then just give up. I dig really hard on an elegant
                     * way to add a new ContentInstant for a missing frame,
                     * but found no satisfying way. There is no good way to
                     * add spots to an empty frame. The way I found is very
                     * similar to closing the 3D viewer and re-opening it,
                     * therefore I let the user do it.
                     *
                     * So because of this, the SpotDisplayer3D is only a
                     * partial ModelListener.
                     */
                    System.err.println(
                        "[SpotDisplayer3D] The TrackMate 3D viewer cannot deal with adding a spot to an empty frame.");
                  } else {
                    spotGroupNode.add(spot, center, color);
                  }

                  break;
                }

              case ModelChangeEvent.FLAG_SPOT_FRAME_CHANGED:
                {

                  // Where did it belonged?
                  Integer targetFrame = -1;
                  for (final Integer f : blobs.keySet()) {
                    if (blobs.get(f).centers.containsKey(spot)) {
                      targetFrame = f;
                      break;
                    }
                  }

                  if (targetFrame < 0) {
                    System.err.println(
                        "[SpotDisplayer3D] Could not find the frame spot " + spot + " belongs to.");
                    return;
                  }

                  blobs.get(targetFrame).remove(spot);
                  // Sphere location and radius
                  final double[] coords = new double[3];
                  TMUtils.localize(spot, coords);
                  final Double radius = spot.getFeature(Spot.RADIUS);
                  final double[] pos =
                      new double[] {coords[0], coords[1], coords[2], radius * radiusRatio};
                  final Point4d center = new Point4d(pos);

                  // Sphere color
                  final Color4f color = new Color4f(spotColorGenerator.color(spot));
                  color.w = 0;
                  if (null == spotGroupNode) {
                    /*
                     * We then just give up. See above.
                     */
                    System.err.println(
                        "[SpotDisplayer3D] The TrackMate 3D viewer cannot deal with moving a spot to an empty frame.");
                  } else {
                    spotGroupNode.add(spot, center, color);
                  }
                  break;
                }

              case ModelChangeEvent.FLAG_SPOT_MODIFIED:
                {
                  if (null != spotGroupNode) {
                    spotGroupNode.remove(spot);
                    // Sphere location and radius
                    final double[] coords = new double[3];
                    TMUtils.localize(spot, coords);
                    final Double radius = spot.getFeature(Spot.RADIUS);
                    final double[] pos =
                        new double[] {coords[0], coords[1], coords[2], radius * radiusRatio};
                    final Point4d center = new Point4d(pos);

                    // Sphere color
                    final Color4f color = new Color4f(spotColorGenerator.color(spot));
                    color.w = 0;
                    spotGroupNode.add(spot, center, color);
                  }
                  break;
                }

              default:
                {
                  System.err.println("[SpotDisplayer3D] Unknown spot flag ID: " + spotFlag);
                }
            }
          }

          /*
           * Deal with edges
           */

          for (final DefaultWeightedEdge edge : event.getEdges()) {
            final int edgeFlag = event.getEdgeFlag(edge);
            switch (edgeFlag) {
              case ModelChangeEvent.FLAG_EDGE_ADDED:
              case ModelChangeEvent.FLAG_EDGE_MODIFIED:
              case ModelChangeEvent.FLAG_EDGE_REMOVED:
                {
                  if (null == trackNode) {
                    trackContent = makeTrackContent();
                    universe.removeContent(TRACK_CONTENT_NAME);
                    universe.addContent(trackContent);
                  } else {
                    trackNode.makeMeshes();
                    updateTrackColors();
                  }
                  break;
                }

              default:
                {
                  System.err.println("[SpotDisplayer3D] Unknown edge flag ID: " + edgeFlag);
                }
            }
          }
          break;
        }

      default:
        {
          System.err.println("[SpotDisplayer3D] Unknown event ID: " + event.getEventID());
        }
    }
  }
 @Override
 public void refresh() {
   if (null != trackNode) trackNode.refresh();
 }