public void setCamera(Camera cameraParam) {
    camera = cameraParam;

    // set constants for terminating voxel traversal early if projected voxel size is less than 1
    // pixel on the screen
    voxelSizeConstantA = camera.getDistanceToViewplane() * screenWidth;
    voxelSizeConstantB = camera.getViewplaneTop().length() / 2;
  }
  public void render(Graphics g) {
    // make a copy of the camera to prevent artefacts from moving the camera during rendering
    Camera cameraFrame = new Camera(camera);

    // increment the number of frames rendered
    frameIndex++;

    // set constants for terminating voxel traversal early if projected voxel size is less than 1
    // pixel on the screen. This small calculation is done once a
    // frame in case the camera's FOV has changed.
    voxelSizeConstantA = cameraFrame.getDistanceToViewplane() * screenWidth;
    voxelSizeConstantB = cameraFrame.getViewplaneTop().length() * 0.5d;

    // set the skipNode for this frame
    setSkipNode(cameraFrame.getPosition());

    // make a thread executor to execute node rendering threads - one for each pixel
    ExecutorService rendererExecutor = Executors.newFixedThreadPool(NTHREADS);

    // for every pixel on the screen, cast a ray through it at the octreeModel
    for (int row = 0; row < screenHeight; row++) {
      for (int col = 0; col < screenWidth; col++) {
        Ray cameraRay =
            new Ray(
                cameraFrame.getPosition(),
                cameraFrame.getVectorToPixel(col, row, screenWidth, screenHeight));

        if (skipNode != null) {
          RayCast rc =
              new RayCast(
                  skipNode,
                  skipNodeBoxMin,
                  skipNodeBoxDim,
                  cameraRay,
                  row * screenHeight + col,
                  imageColors,
                  imageDepth,
                  subdivider,
                  voxelSizeConstantA,
                  voxelSizeConstantB);
          rendererExecutor.execute(rc);
        } else {
          RayCast rc =
              new RayCast(
                  rootNode,
                  new Vector3d(-1, -1, -1),
                  2,
                  cameraRay,
                  row * screenHeight + col,
                  imageColors,
                  imageDepth,
                  subdivider,
                  voxelSizeConstantA,
                  voxelSizeConstantB);
          rendererExecutor.execute(rc);
        }
      }
    }

    // shutdown the executor and wait for all threads to terminate
    rendererExecutor.shutdown();
    try {
      rendererExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    // do SSAO calculations and put results in imageShadows and get minimum tMin
    optTmin = SSAO.setOcclusion(imageDepth, screenHeight, screenWidth, imageShadows);

    // set the optimal tmin value to be used by camera movement
    optTmin =
        imageDepth[(screenWidth / 2) * screenHeight + (screenWidth / 2)] == Double.MAX_VALUE
            ? optTmin
            : imageDepth[(screenWidth / 2) * screenHeight + (screenWidth / 2)];

    // adjust the colors in imageColors by the lighting in imageShadows to output the final colors
    // in imagePixelData
    for (int row = 0; row < screenHeight; row++)
      for (int col = 0; col < screenWidth; col++)
        imagePixelData[row * screenHeight + col] =
            ColorUtils.adjustLight(
                imageColors[row * screenHeight + col], imageShadows[row * screenHeight + col]);

    // save image if recording is enabled
    if (isRecording()) {
      try {
        File outputfile = new File("images/screenshot" + frameIndex + ".png");
        ImageIO.write(backbuffer, "png", outputfile);
      } catch (IOException e) {
        System.err.println("file write IO exception");
      }
    }

    // draw the image onto the graphics
    g.drawImage(backbuffer, 0, 0, null);
    backbuffer.flush();

    // register that a frame has been drawn
    metrics.registerFrameRender();

    // unify the bricks every 10 frames
    if (frameIndex % 10 == 0) brickManager.unifyBricks();
  }