/* * 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); }
/* * 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(); }
/* * 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(); }
/** * $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);
/* * $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;
/* * $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); }
/** * $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; } }
/* * 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); } }
/* * 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()); } } }
/* * 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); } }
/* * $example: Construct a new renderer. */ final ExampleRendererType renderer = new ExampleRenderer();
/* * $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();
/* * $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));
/* * $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(); } }