io7m
com.io7m.jcamera 0.6.0
2.2
First-person camera

- 2.2.1. Overview
- 2.2.2. FPS Simulation
- 2.2.3. Input
- 2.2.4. Rendering/Interpolation
- 2.2.5. Main

2.2.1
Overview
1
In order to aid comprehension, and to follow good software engineering practices, the usage example here will be developed as a set of types with well-defined interfaces. Specifically, a simple main program will initialize an OpenGL window with JOGL, and register some keyboard and mouse listeners to supply input to a simulation, which in turn periodically produces new data (a view matrix) for a renderer. The renderer draws a simple static scene using the view matrix periodically produced by a camera from the com.io7m.jcamera package. The simulation runs at a fixed time step to provide completely frame rate independent movement (see the article "Fix Your Timestep!" for details on why physical simulations should use fixed time steps). The renderer, however, runs at an arbitrary frame rate. On some systems the frame rate will be exactly equal to the screen's vertical refresh rate, whilst on others, the rate will be thousands of times per second. The desire is to show that the system works equally well no matter what frame rate is used, so no attempt is made to enforce any particular rate. A rough graph of the data flow between components is as follows:
2.2.1.1. Example data flow
2
JOGL works with an event-based model, where mouse and keyboard input causes events to be delivered to mouse and keyboard listeners. Additionally, OpenGL rendering typically occurs via an OpenGL listener: JOGL (or the GPU, or the operating system, whichever is responsible) indicates that it is time to render the scene, and the user's registered listener is executed to actually perform the drawing. It's reasonable to assume that input handling and rendering occur on different threads by default, so the code here is careful to operate in a thread-safe manner.
3
The interface exposed to JOGL by the example renderer is as follows [ ExampleRendererType.java]:
2.2.1.2. Renderer interface

            /*
     * Copyright © 2021 Mark Raynsford <code@io7m.com> https://www.io7m.com
     *
     * Permission to use, copy, modify, and/or distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
     * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
     * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */

    package com.io7m.jcamera.examples.jogl;

    import com.io7m.jcamera.JCameraReadableSnapshotType;
    import com.io7m.jtensors.core.unparameterized.vectors.Vector3D;
    import com.jogamp.newt.opengl.GLWindow;
    import com.jogamp.opengl.GL3;

    import java.io.IOException;
    import java.util.Optional;

    /**
     * The interface exposed by the renderer to JOGL.
     */

    public interface ExampleRendererType extends ExampleRendererControllerType
    {
      /**
       * Initialize the scene, using the given window and OpenGL interface.
       *
       * @param in_window The window
       * @param in_gl     The OpenGL interface
       *
       * @throws IOException On I/O errors
       */

      void init(
        GLWindow in_window,
        GL3 in_gl)
        throws IOException;

      /**
       * Draw the scene.
       *
       * @param s      A camera snapshot
       * @param target An optional target to be drawn
       */

      void draw(
        JCameraReadableSnapshotType s,
        Optional<Vector3D> target);

      /**
       * Indicate that the screen has been resized.
       *
       * @param width  The new width
       * @param height The new height
       */

      void reshape(
        int width,
        int height);
    }

4
Little is needed in the way of explanation here. The renderer initializes the scene's resources when init is called (such as allocating memory on the GPU for mesh data, compiling shaders, etc). The renderer draws the scene whenever draw is called, and recalculates any internal resources that are dependent on the size of the window (such as the scene's projection matrix) when reshape is called.
5
The actual implementation of the renderer is of little interest here. It simply draws a hundred or so static quads from the perspective of whatever is the current view matrix. The implementation is given in ExampleRenderer and will not be referenced again.
2.2.2
FPS Simulation
1
The interface exposed to JOGL by the example simulation is as follows [ ExampleFPSStyleSimulationType.java]:
2.2.2.1. Simulation interface

            /*
     * Copyright © 2021 Mark Raynsford <code@io7m.com> https://www.io7m.com
     *
     * Permission to use, copy, modify, and/or distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
     * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
     * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */

    package com.io7m.jcamera.examples.jogl;

    import com.io7m.jcamera.JCameraFPSStyleInputType;
    import com.io7m.jcamera.JCameraFPSStyleIntegratorType;
    import com.io7m.jcamera.JCameraFPSStyleSnapshot;
    import com.io7m.jcamera.JCameraFPSStyleType;

    /**
     * The interface to simulations (with fps-style cameras) exposed to JOGL.
     */

    public interface ExampleFPSStyleSimulationType
    {
      /**
       * @return {@code true} if the camera is enabled.
       */

      boolean cameraIsEnabled();

      /**
       * Enable/disable the camera.
       *
       * @param b {@code true} if the camera should be enabled.
       */

      void cameraSetEnabled(
        boolean b);

      /**
       * @return The camera used for the simulation.
       */

      JCameraFPSStyleType getCamera();

      /**
       * @return The simulation delta time
       */

      float getDeltaTime();

      /**
       * @return The camera input
       */

      JCameraFPSStyleInputType getInput();

      /**
       * @return The integrator used for the camera.
       */

      JCameraFPSStyleIntegratorType getIntegrator();

      /**
       * @return A new camera snapshot
       */

      JCameraFPSStyleSnapshot integrate();
    }

2
Again, little is needed in the way of explanation. The simulation provides a camera that can be enabled and disabled. If the camera is disabled, a simple fixed camera is used rather than having the camera be driven by keyboard and mouse input.
3
The simulation needs a way to periodically warp the mouse cursor to the center of the screen if the movable camera is enabled, so the renderer exposes the following interface to the simulation [ ExampleRendererControllerType.java]:
2.2.2.2. Renderer control interface

            /*
     * Copyright © 2021 Mark Raynsford <code@io7m.com> https://www.io7m.com
     *
     * Permission to use, copy, modify, and/or distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
     * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
     * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */

    package com.io7m.jcamera.examples.jogl;

    /**
     * The interface that the simulation uses to talk to the renderer.
     */

    public interface ExampleRendererControllerType
    {
      /**
       * Tell the renderer/windowing system that it should warp the pointer to the
       * center of the screen.
       */

      void sendWantWarpPointer();
    }

4
The actual implementation of the simulation is as follows [ ExampleFPSStyleSimulation.java]. First:
2.2.2.3. Camera declarations and storage

              /**
       * $example: Construct a new simulation.
       *
       * @param in_renderer The interface to the renderer
       */

      public ExampleFPSStyleSimulation(
        final ExampleRendererControllerType in_renderer)
      {
        this.renderer = in_renderer;
        this.input = JCameraFPSStyleInput.newInput();
        this.camera = JCameraFPSStyle.newCamera();
        final JCameraFPSStyleType camera_fixed = JCameraFPSStyle.newCamera();
        this.fixed_snapshot = JCameraFPSStyleSnapshots.of(camera_fixed);
        this.camera_enabled = new AtomicBoolean(false);

5
Then, an integrator is created to drive the camera, and the integration period required for a fixed time step of 60 frames per second is calculated:
2.2.2.4. Integrator

                /*
         * $example: Construct an integrator using the default implementations.
         */

        this.integrator =
          JCameraFPSStyleIntegrator.newIntegrator(this.camera, this.input);

        /*
         * Work out what fraction of a second the given simulation rate is going
         * to require.
         */

        final float rate = 60.0f;
        this.integrator_time_seconds = 1.0f / rate;

6
Finally, to give the camera somewhat more snappy and abrupt behaviour than the default settings, some new acceleration and drag values are configured for the camera:
2.2.2.5. Acceleration and Drag

                /*
         * $example: Configure the integrator. Use a high drag factor to give
         * quite abrupt stops, and use high rotational acceleration.
         */

        this.integrator.integratorAngularSetDragHorizontal(0.000000001);
        this.integrator.integratorAngularSetDragVertical(0.000000001);
        this.integrator.integratorAngularSetAccelerationHorizontal(
          Math.PI / 12.0 / (double) this.integrator_time_seconds);
        this.integrator.integratorAngularSetAccelerationVertical(
          Math.PI / 12.0 / (double) this.integrator_time_seconds);
        this.integrator.integratorLinearSetAcceleration(
          3.0 / (double) this.integrator_time_seconds);
        this.integrator.integratorLinearSetMaximumSpeed(3.0);
        this.integrator.integratorLinearSetDrag(0.000000001);
      }

7
The integrate function is executed at a rate of 60 times per second, and produces a new snapshot each time, which is passed to the renderer. The immutable nature of the snapshot means that it can be safely shared across threads without any need for locks or other synchronization. If the camera is actually enabled, the simulation also instructs the renderer to warp the mouse cursor back to the center of the screen. The rest of the functions complete the interface.
2.2.2.6. Integrate

              /**
       * $example: Integrate the camera.
       *
       * @return A new camera snapshot.
       */

      @Override
      public JCameraFPSStyleSnapshot integrate()
      {
        /*
         * If the camera is actually enabled, integrate and produce a snapshot,
         * and then tell the renderer/window system that it should warp the
         * pointer back to the center of the screen.
         */

        if (this.cameraIsEnabled()) {
          this.integrator.integrate(this.integrator_time_seconds);
          final JCameraFPSStyleSnapshot snap =
            JCameraFPSStyleSnapshots.of(this.camera);
          this.renderer.sendWantWarpPointer();
          return snap;
        }

        return this.fixed_snapshot;
      }

      @Override
      public boolean cameraIsEnabled()
      {
        return this.camera_enabled.get();
      }

      @Override
      public void cameraSetEnabled(
        final boolean b)
      {
        this.camera_enabled.set(b);
      }

      @Override
      public float getDeltaTime()
      {
        return this.integrator_time_seconds;
      }

      @Override
      public JCameraFPSStyleInputType getInput()
      {
        return this.input;
      }

      @Override
      public JCameraFPSStyleIntegratorType getIntegrator()
      {
        return this.integrator;
      }

      @Override
      public JCameraFPSStyleType getCamera()
      {
        return this.camera;
      }
    }

2.2.3
Input
1
It's now necessary to supply the simulation with input.
2
A KeyListener is defined. Every time the user presses or releases a key, the simulation camera's input is notified accordingly.
3
There is one main issue covered here: If the user has keyboard auto-repeat enabled by their operating system, holding a key will result in an endless stream of "key pressed" and "key released" events. The code here is only interested in receiving the first "key pressed" and last "key released" event for each key, and JOGL's NEWT system marks each event as having been produced by auto-repeat (or not). Therefore, the auto-repeat flag is checked for each event, and the event is discarded if the flag is set.
4
Additionally, a few extra definitions allow for showing/hiding the mouse cursor, and switching between windowed and full-screen mode. JOGL requires that the setFullscreen function be called on a background thread, rather than the thread handling input and/or rendering for the current window.
2.2.3.1. Main (KeyListener)

            /*
     * Copyright © 2021 Mark Raynsford <code@io7m.com> https://www.io7m.com
     *
     * Permission to use, copy, modify, and/or distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
     * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
     * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */

    package com.io7m.jcamera.examples.jogl;

    import com.io7m.jcamera.JCameraFPSStyleInputType;
    import com.jogamp.newt.event.InputEvent;
    import com.jogamp.newt.event.KeyEvent;
    import com.jogamp.newt.event.KeyListener;
    import com.jogamp.newt.opengl.GLWindow;

    import java.util.concurrent.ExecutorService;

    /**
     * The key listener used to handle keyboard events.
     */

    // CHECKSTYLE_JAVADOC:OFF

    @SuppressWarnings("synthetic-access")
    public final class ExampleFPSStyleKeyListener implements
      KeyListener
    {
      private final ExampleFPSStyleSimulationType sim;
      private final ExecutorService background_workers;
      private final ExampleRendererType renderer;
      private final JCameraFPSStyleInputType input;
      private final GLWindow window;

      public ExampleFPSStyleKeyListener(
        final ExampleFPSStyleSimulationType in_sim,
        final ExecutorService in_background_workers,
        final ExampleRendererType in_renderer,
        final GLWindow in_window)
      {
        this.sim = in_sim;
        this.background_workers = in_background_workers;
        this.renderer = in_renderer;
        this.input = in_sim.getInput();
        this.window = in_window;
      }

      @Override
      public void keyPressed(
        final KeyEvent e)
      {
        assert e != null;

        /*
         * Ignore events that are the result of keyboard auto-repeat. This means
         * there's one single event when a key is pressed, and another when it is
         * released (as opposed to an endless stream of both when the key is held
         * down).
         */

        if ((e.getModifiers() & InputEvent.AUTOREPEAT_MASK) == InputEvent.AUTOREPEAT_MASK) {
          return;
        }

        switch (e.getKeyCode()) {

          /*
           * Standard WASD camera controls, with E and Q moving up and down,
           * respectively.
           */

          case KeyEvent.VK_A: {
            this.input.setMovingLeft(true);
            break;
          }
          case KeyEvent.VK_W: {
            this.input.setMovingForward(true);
            break;
          }
          case KeyEvent.VK_S: {
            this.input.setMovingBackward(true);
            break;
          }
          case KeyEvent.VK_D: {
            this.input.setMovingRight(true);
            break;
          }
          case KeyEvent.VK_E: {
            this.input.setMovingUp(true);
            break;
          }
          case KeyEvent.VK_Q: {
            this.input.setMovingDown(true);
            break;
          }
        }
      }

      @Override
      public void keyReleased(
        final KeyEvent e)
      {
        assert e != null;

        /*
         * Ignore events that are the result of keyboard auto-repeat. This means
         * there's one single event when a key is pressed, and another when it is
         * released (as opposed to an endless stream of both when the key is held
         * down).
         */

        if ((e.getModifiers() & InputEvent.AUTOREPEAT_MASK) == InputEvent.AUTOREPEAT_MASK) {
          return;
        }

        switch (e.getKeyCode()) {

          /*
           * Pressing 'M' enables/disables the camera.
           */

          case KeyEvent.VK_M: {
            this.toggleCameraEnabled();
            break;
          }

          /*
           * Pressing 'P' makes the mouse cursor visible/invisible.
           */

          case KeyEvent.VK_P: {
            System.out.printf(
              "Making pointer %s\n",
              this.window.isPointerVisible() ? "invisible" : "visible");
            this.window.setPointerVisible(!this.window.isPointerVisible());
            break;
          }

          /*
           * Pressing enter switches between windowed and fullscreen mode. JOGL
           * requires that this be executed on a background thread.
           */

          case KeyEvent.VK_ENTER: {
            this.background_workers.execute(new Runnable()
            {
              @Override
              public void run()
              {
                final boolean mode =
                  !ExampleFPSStyleKeyListener.this.window.isFullscreen();
                ExampleFPSStyleKeyListener.this.window.setFullscreen(mode);
              }
            });
            break;
          }

          /*
           * Standard WASD camera controls, with E and Q moving up and down,
           * respectively.
           */

          case KeyEvent.VK_A: {
            this.input.setMovingLeft(false);
            break;
          }
          case KeyEvent.VK_W: {
            this.input.setMovingForward(false);
            break;
          }
          case KeyEvent.VK_S: {
            this.input.setMovingBackward(false);
            break;
          }
          case KeyEvent.VK_D: {
            this.input.setMovingRight(false);
            break;
          }
          case KeyEvent.VK_E: {
            this.input.setMovingUp(false);
            break;
          }
          case KeyEvent.VK_Q: {
            this.input.setMovingDown(false);
            break;
          }
        }
      }

      public void toggleCameraEnabled()
      {
        final boolean enabled = this.sim.cameraIsEnabled();

        if (enabled) {
          System.out.println("Disabling camera");
          this.window.confinePointer(false);
        } else {
          System.out.println("Enabling camera");
          this.window.confinePointer(true);
          this.renderer.sendWantWarpPointer();
          this.input.setRotationHorizontal(0.0);
          this.input.setRotationVertical(0.0);
        }

        this.sim.cameraSetEnabled(!enabled);
      }
    }

5
A MouseAdapter is defined. Every time the user moves the mouse, the rotation coefficients are calculated and sent to the simulation camera's input:
2.2.3.2. Main (MouseAdapter)

            /*
     * Copyright © 2021 Mark Raynsford <code@io7m.com> https://www.io7m.com
     *
     * Permission to use, copy, modify, and/or distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
     * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
     * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */

    package com.io7m.jcamera.examples.jogl;

    import com.io7m.jcamera.JCameraFPSStyleInputType;
    import com.io7m.jcamera.JCameraFPSStyleMouseRegion;
    import com.io7m.jcamera.JCameraRotationCoefficientsMutable;
    import com.jogamp.newt.event.MouseAdapter;
    import com.jogamp.newt.event.MouseEvent;

    import java.util.concurrent.atomic.AtomicReference;

    /**
     * The mouse adapter used to handle mouse events.
     */

    // CHECKSTYLE_JAVADOC:OFF

    public final class ExampleFPSStyleMouseAdapter extends MouseAdapter
    {
      private final AtomicReference<JCameraFPSStyleMouseRegion> mouse_region;
      private final JCameraFPSStyleInputType input;
      private final ExampleFPSStyleSimulationType sim;
      private final JCameraRotationCoefficientsMutable rotations;

      public ExampleFPSStyleMouseAdapter(
        final AtomicReference<JCameraFPSStyleMouseRegion> in_mouse_region,
        final ExampleFPSStyleSimulationType in_sim,
        final JCameraRotationCoefficientsMutable in_rotations)
      {
        this.mouse_region = in_mouse_region;
        this.input = in_sim.getInput();
        this.sim = in_sim;
        this.rotations = in_rotations;
      }

      @Override
      public void mouseMoved(
        final MouseEvent e)
      {
        assert e != null;

        /*
         * If the camera is enabled, get the rotation coefficients for the mouse
         * movement.
         */

        if (this.sim.cameraIsEnabled()) {
          this.rotations.from(
            this.mouse_region.get().coefficients(
              e.getX(),
              e.getY()));
          this.input.addRotationAroundHorizontal(this.rotations.horizontal());
          this.input.addRotationAroundVertical(this.rotations.vertical());
        }
      }
    }

2.2.4
Rendering/Interpolation
1
A GLEventListener is added to the window. The listener will tell the renderer to draw the scene every time the OpenGL implementation requires a new frame.
2
The display method linearly interpolates between the most recently received camera snapshots in order to provide smooth animation independent of the simulation and/or frame rate.
2.2.4.1. Main (GLEventListener)

            /*
     * Copyright © 2021 Mark Raynsford <code@io7m.com> https://www.io7m.com
     *
     * Permission to use, copy, modify, and/or distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
     * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
     * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */

    package com.io7m.jcamera.examples.jogl;

    import com.io7m.jcamera.JCameraFPSStyleMouseRegion;
    import com.io7m.jcamera.JCameraFPSStyleSnapshot;
    import com.io7m.jcamera.JCameraFPSStyleSnapshots;
    import com.io7m.jcamera.JCameraScreenOrigin;
    import com.jogamp.newt.opengl.GLWindow;
    import com.jogamp.opengl.DebugGL3;
    import com.jogamp.opengl.GL;
    import com.jogamp.opengl.GL3;
    import com.jogamp.opengl.GLAutoDrawable;
    import com.jogamp.opengl.GLEventListener;

    import java.io.IOException;
    import java.util.Optional;
    import java.util.concurrent.atomic.AtomicReference;

    /**
     * The GL event listener used to handle rendering and driving of the
     * simulation.
     */

    // CHECKSTYLE_JAVADOC:OFF

    public final class ExampleFPSStyleGLListener implements GLEventListener
    {
      private final GLWindow window;
      private final ExampleFPSStyleSimulationType sim;
      private final AtomicReference<JCameraFPSStyleMouseRegion> mouse_region;
      private final ExampleRendererType renderer;
      private long time_then;
      private double time_accum;
      private JCameraFPSStyleSnapshot snap_curr;
      private JCameraFPSStyleSnapshot snap_prev;

      public ExampleFPSStyleGLListener(
        final GLWindow in_window,
        final JCameraFPSStyleSnapshot in_snap,
        final ExampleFPSStyleSimulationType in_sim,
        final AtomicReference<JCameraFPSStyleMouseRegion> in_mouse_region,
        final ExampleRendererType in_renderer)
      {
        this.window = in_window;
        this.sim = in_sim;
        this.mouse_region = in_mouse_region;
        this.renderer = in_renderer;
        this.snap_curr = in_snap;
        this.snap_prev = in_snap;
      }

      /**
       * Initialize the simulation.
       *
       * @param drawable The OpenGL drawable
       */

      @Override
      public void init(
        final GLAutoDrawable drawable)
      {
        try {
          assert drawable != null;

          final GL3 g = new DebugGL3(drawable.getGL().getGL3());
          assert g != null;

          this.time_then = System.nanoTime();
          this.renderer.init(this.window, g);
          this.renderer.reshape(this.window.getWidth(), this.window.getHeight());
        } catch (final IOException e) {
          throw new RuntimeException(e);
        }
      }

      @Override
      public void dispose(
        final GLAutoDrawable drawable)
      {
        // Nothing.
      }

      @Override
      public void display(
        final GLAutoDrawable drawable)
      {
        assert drawable != null;

        /*
         * Integrate the camera as many times as necessary for each rendering
         * frame interval.
         */

        final long time_now = System.nanoTime();
        final long time_diff = time_now - this.time_then;
        final double time_diff_s = (double) time_diff / 1000000000.0;
        this.time_accum = this.time_accum + time_diff_s;
        this.time_then = time_now;

        final float sim_delta = this.sim.getDeltaTime();
        while (this.time_accum >= (double) sim_delta) {
          this.snap_prev = this.snap_curr;
          this.snap_curr = this.sim.integrate();
          this.time_accum -= sim_delta;
        }

        /*
         * Determine how far the current time is between the current camera state
         * and the next, and use that value to interpolate between the two saved
         * states.
         */

        final float alpha = (float) (this.time_accum / (double) sim_delta);
        final JCameraFPSStyleSnapshot snap_interpolated =
          JCameraFPSStyleSnapshots.interpolate(
            this.snap_prev,
            this.snap_curr,
            alpha);

        final GL3 g = new DebugGL3(drawable.getGL().getGL3());
        assert g != null;
        g.glClear(GL.GL_COLOR_BUFFER_BIT);

        /*
         * Draw the scene!
         */

        this.renderer.draw(snap_interpolated, Optional.empty());
      }

      @Override
      public void reshape(
        final GLAutoDrawable drawable,
        final int x,
        final int y,
        final int width,
        final int height)
      {
        this.mouse_region.set(JCameraFPSStyleMouseRegion.of(
          JCameraScreenOrigin.SCREEN_ORIGIN_TOP_LEFT,
          width,
          height));
        this.renderer.reshape(width, height);
      }
    }

2.2.5
Main
1
All that remains is to have JOGL tell the renderer when to render, and to supply the simulation with input in order to move and orient the camera according to mouse and keyboard input. The main program constructs a new renderer:
2.2.5.1. Main (Renderer)

                /*
         * $example: Construct a new renderer.
         */

        final ExampleRendererType renderer = new ExampleRenderer();

2
Then, a new simulation is constructed, and is passed a reference to the renderer (the type of the simulation constructor is declared such that it only sees a small subset of the interface exposed by the renderer). The simulation camera's input is also retrieved:
2.2.5.2. Main (Simulation)

                /*
         * $example: Construct a new simulation and produce an initial snapshot of
         * the camera for later use.
         */

        final ExampleFPSStyleSimulationType sim =
          new ExampleFPSStyleSimulation(renderer);
        final JCameraFPSStyleSnapshot snap = sim.integrate();

3
Some storage is allocated, and a mouse region is created to map mouse movements to rotational coefficients:
2.2.5.3. Main (Mouse region and coefficients)

                /*
         * $example: Declare a structure to hold mouse rotation coefficients, and
         * a mouse region configured with an origin that matches that of JOGL's
         * windowing system.
         */

        final JCameraRotationCoefficientsMutable rotations =
          JCameraRotationCoefficientsMutable.create();
        final AtomicReference<JCameraFPSStyleMouseRegion> mouse_region =
          new AtomicReference<>(
            JCameraFPSStyleMouseRegion.of(
              JCameraScreenOrigin.SCREEN_ORIGIN_TOP_LEFT,
              640.0,
              480.0));

4
An OpenGL window is created, an Animator is constructed that will tell the window to refresh frequently, and the previously defined listeners are attached to the window.
2.2.5.4. Main (Window and animator)

                /*
         * $example: Initialize JOGL and open a window, construct an animator to
         * regularly refresh the screen, and assign GL event listener, mouse
         * listener, and keyboard listener.
         */

        final GLProfile profile = GLProfile.get(GLProfile.GL3);
        final GLCapabilities caps = new GLCapabilities(profile);
        final GLWindow window = GLWindow.create(caps);
        window.setSize(640, 480);
        window.setTitle(ExampleFPSStyleMain.class.getCanonicalName());

        final Animator anim = new Animator();
        anim.add(window);

        window.addGLEventListener(new ExampleFPSStyleGLListener(
          window,
          snap,
          sim,
          mouse_region,
          renderer));

        window.addMouseListener(new ExampleFPSStyleMouseAdapter(
          mouse_region,
          sim,
          rotations));

        window.addKeyListener(new ExampleFPSStyleKeyListener(
          sim,
          background_workers,
          renderer,
          window));

        /*
         * Close the program when the window closes.
         */

        window.addWindowListener(new WindowAdapter()
        {
          @Override
          public void windowDestroyed(
            final WindowEvent e)
          {
            System.out.println("Stopping animator");
            anim.stop();
            System.out.println("Exiting");
            System.exit(0);
          }
        });

        window.setDefaultCloseOperation(
          WindowClosingProtocol.WindowClosingMode.DISPOSE_ON_CLOSE);
        window.setVisible(true);

        /*
         * Start everything running.
         */

        anim.start();
      }
    }

2.2.5.5. Example screenshot