private void followRay(
      SimpleVector pos,
      SimpleVector dir,
      double energyEV,
      FloatProcessor imp,
      int scatterCount,
      double totalDistance) {
    if (energyEV <= 1 || scatterCount > 20000) {
      System.out.println("energy low, times scattered: " + scatterCount);
      return;
    }
    // follow ray until next interaction point
    SimpleVector oldPos = pos.clone();
    double dist = sampler.getDistanceUntilNextInteractionCm(material, energyEV);
    pos.add(dir.multipliedBy(dist));
    pathlengths.add(dist);
    // draw the entire path
    // imp.drawLine((int)(scale*oldPos.getElement(0)), (int)(scale*oldPos.getElement(1)),
    // (int)(scale*pos.getElement(0)), (int)(scale*pos.getElement(1)));
    // draw interaction points only
    imp.drawDot((int) (scale * pos.getElement(0)), (int) (scale * pos.getElement(1)));

    // choose compton or photoelectric effect
    double photo =
        material.getAttenuation(energyEV / 1000, AttenuationType.PHOTOELECTRIC_ABSORPTION);
    double compton =
        material.getAttenuation(energyEV / 1000, AttenuationType.INCOHERENT_ATTENUATION);

    if (sampler.random() * (photo + compton) <= photo) {
      // photoelectric absorption
      energyEV = 0;
      // System.out.println("absorbed after " + scatterCount + " collisions");
      xs.add(pos.getElement(0));
      ys.add(pos.getElement(1));
      zs.add(pos.getElement(2));

      return;
    } else {
      // compton scattering

      energyEV = sampler.sampleComptonScattering(energyEV, dir);

      // send new ray
      followRay(pos, dir, energyEV, imp, scatterCount + 1, totalDistance + dist);
    }
  }
  private void visualizeKleinNishina(double energyJoule) {
    int gridWidth = 600;
    int gridHeight = 500;
    double maxAngle = 360;

    Grid2D grid = new Grid2D(gridWidth, gridHeight);

    FloatProcessor fp = new FloatProcessor(grid.getWidth(), grid.getHeight());
    fp.setPixels(grid.getBuffer());

    // find max value
    double max = 0;
    for (int i = 0; i < maxAngle; ++i) {
      double value = XRayTracerSampling.comptonAngleProbability(energyJoule, Math.toRadians(i));

      if (value > max) {
        max = value;
      }
    }

    fp.drawLine((int) 0, grid.getHeight() / 2, grid.getWidth(), grid.getHeight() / 2);
    fp.drawLine(grid.getWidth() / 2, 0, grid.getWidth() / 2, grid.getHeight());
    fp.drawOval((grid.getWidth() - grid.getHeight()) / 2, 0, grid.getHeight(), grid.getHeight());
    double lastX = 0;
    double lastY = 0;

    // draw angle distribution
    for (int i = 0; i < maxAngle; ++i) {
      double value = XRayTracerSampling.comptonAngleProbability(energyJoule, Math.toRadians(i));

      SimpleMatrix m = Rotations.createBasicZRotationMatrix(Math.toRadians(i));
      SimpleVector v = new SimpleVector(((value / max) * ((double) grid.getHeight() / 2.d)), 0, 0);

      SimpleVector r = SimpleOperators.multiply(m, v);

      double x = grid.getWidth() / 2 + r.getElement(0);
      double y = grid.getHeight() / 2 + r.getElement(1);
      if (i > 0) fp.drawLine((int) lastX, (int) lastY, (int) x, (int) y);
      lastX = x;
      lastY = y;
    }

    grid.show(
        "Normalized Klein-Nishina cross-section as a function of scatter angle ("
            + energyJoule / eV
            + "eV)");

    grid = new Grid2D(gridWidth, gridHeight);

    fp = new FloatProcessor(grid.getWidth(), grid.getHeight());
    fp.setPixels(grid.getBuffer());

    // draw histogram with rejection sampling of the klein-nishina
    // distribution
    int[] angles = new int[grid.getWidth()];
    for (int i = 0; i < numSamples; ++i) {
      double angle = Math.toDegrees(sampler.getComptonAngleTheta(energyJoule));
      int pos = (int) (angle * grid.getWidth() / 360.d);
      angles[pos] += 1;
    }
    double max2 = 0;
    for (int i = 0; i < angles.length; ++i) {
      if (angles[i] > max2) {
        max2 = angles[i];
      }
    }
    for (int i = 0; i < angles.length; ++i) {
      double x = i;
      double y = ((angles[i]) * (grid.getHeight() / (max2)));
      fp.drawLine((int) x, (int) grid.getHeight(), (int) x, grid.getHeight() - (int) y);
    }

    // draw klein-nishina probability function
    lastX = 0;
    lastY = 0;
    for (int i = 0; i < maxAngle; ++i) {
      double value = XRayTracerSampling.comptonAngleProbability(energyJoule, Math.toRadians(i));
      double x = (i * (grid.getWidth() / 360.f));
      double y = grid.getHeight() - ((value) * (grid.getHeight() / (max)));

      fp.drawLine((int) lastX, (int) lastY, (int) x, (int) y);
      lastX = x;
      lastY = y;
    }

    grid.show(
        "Energy: " + energyJoule / eV + "eV; x-axis: angle[0-360]; y-axis: probability[0-max]");
  }