Skip to content

Commit

Permalink
Add gamepad input processing (left/right sticks so far)
Browse files Browse the repository at this point in the history
  • Loading branch information
ultraq committed Jun 16, 2024
1 parent 40e4cec commit 28f8b1e
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import nz.net.ultraq.redhorizon.engine.graphics.opengl.OpenGLContext
import nz.net.ultraq.redhorizon.engine.graphics.opengl.OpenGLRenderer
import nz.net.ultraq.redhorizon.engine.graphics.opengl.OpenGLWindow
import nz.net.ultraq.redhorizon.engine.graphics.pipeline.RenderPipeline
import nz.net.ultraq.redhorizon.engine.input.GamepadStateProcessor
import nz.net.ultraq.redhorizon.engine.input.InputEventStream
import nz.net.ultraq.redhorizon.engine.input.KeyEvent
import nz.net.ultraq.redhorizon.engine.input.MouseButtonEvent
Expand Down Expand Up @@ -57,7 +58,6 @@ class GraphicsSystem extends EngineSystem implements GraphicsRequests {
private OpenGLRenderer renderer
private ImGuiLayer imGuiLayer
private RenderPipeline renderPipeline

private boolean shouldToggleFullScreen
private boolean shouldToggleVsync
private long lastClickTime
Expand Down Expand Up @@ -184,6 +184,8 @@ class GraphicsSystem extends EngineSystem implements GraphicsRequests {
Thread.currentThread().name = 'Graphics System'
logger.debug('Starting graphics system')

var gamepadStateProcessor = new GamepadStateProcessor(inputEventStream)

// Initialization
new OpenGLContext(windowTitle, config).withCloseable { context ->
window = context.window
Expand Down Expand Up @@ -237,6 +239,7 @@ class GraphicsSystem extends EngineSystem implements GraphicsRequests {
pipeline.render()
window.swapBuffers()
window.pollEvents()
gamepadStateProcessor.process()
}
catch (InterruptedException ignored) {
break
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
/*
* Copyright 2021, Emanuel Rabina (http://www.ultraq.net.nz/)
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -24,7 +24,7 @@ import ch.qos.logback.core.encoder.Encoder
/**
* A custom logback appender made for moving logged events to the debug overlay
* created using ImGui.
*
*
* @author Emanuel Rabina
*/
class ImGuiLoggingAppender<E> extends UnsynchronizedAppenderBase<E> implements EventTarget {
Expand All @@ -45,13 +45,19 @@ class ImGuiLoggingAppender<E> extends UnsynchronizedAppenderBase<E> implements E
@Override
protected void append(E eventObject) {

def message = new String(encoder.encode(eventObject))
var message = new String(encoder.encode(eventObject))
if (eventObject.message.contains('average time')) {
trigger(new ImGuiLogEvent(
message: message,
persistentKey: eventObject.argumentArray[0]
))
}
else if (eventObject.loggerName.contains('GamepadStateProcessor')) {
trigger(new ImGuiLogEvent(
message: message,
persistentKey: eventObject.message
))
}
else {
trigger(new ImGuiLogEvent(
message: message
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2024, Emanuel Rabina (http://www.ultraq.net.nz/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package nz.net.ultraq.redhorizon.engine.input

import groovy.transform.TupleConstructor

/**
* Event emitted for gamepad axis controls.
*
* @author Emanuel Rabina
*/
@TupleConstructor(defaults = false)
class GamepadAxisEvent extends InputEvent {

final int type
final float value
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2024, Emanuel Rabina (http://www.ultraq.net.nz/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package nz.net.ultraq.redhorizon.engine.input

import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_LEFT_X
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y

import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType

/**
* Gamepad-specific control class.
*
* @author Emanuel Rabina
*/
class GamepadControl extends Control<GamepadAxisEvent> {

private final int type
private final Closure handler

/**
* Create a new gamepad control to act on the given gamepad axis events.
*/
GamepadControl(int type, String name,
@ClosureParams(value = SimpleType, options = 'float') Closure handler) {

super(GamepadAxisEvent, name, determineBindingName(type))
this.type = type
this.handler = handler
}

/**
* Return a string representation of the axis binding.
*/
private static String determineBindingName(int type) {

return switch (type) {
case GLFW_GAMEPAD_AXIS_LEFT_X -> 'Left stick X axis'
case GLFW_GAMEPAD_AXIS_LEFT_Y -> 'Left stick Y axis'
default -> '(unknown)'
}
}

@Override
void handleEvent(GamepadAxisEvent event) {

if (event.type == type) {
handler(event.value)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2024, Emanuel Rabina (http://www.ultraq.net.nz/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package nz.net.ultraq.redhorizon.engine.input

import nz.net.ultraq.redhorizon.engine.graphics.GraphicsSystem

import org.lwjgl.glfw.GLFWGamepadState
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import static org.lwjgl.glfw.GLFW.*

import groovy.transform.TupleConstructor
import java.nio.FloatBuffer

/**
* A class for managing gamepad inputs and emitting them as events so that it
* all acts the same for those on the other end of the {@link InputEventStream}.
* <p>
* GLFW currently doesn't have a callback system in place for joysticks, and so
* you have to do DIY polling and handling. See https://github.com/glfw/glfw/issues/601,
* which is unlikely to be solved any time soon.
* <p>
* Joystick/Gamepad processing is currently restricted to the same thread as the
* GLFW context, so this class is only used from the {@link GraphicsSystem}.
*
* @author Emanuel Rabina
*/
@TupleConstructor(defaults = false)
class GamepadStateProcessor {

private static final Logger logger = LoggerFactory.getLogger(GamepadStateProcessor)

final InputEventStream inputEventStream

@Lazy
private GLFWGamepadState gamepadState = { GLFWGamepadState.create() }()

/**
* Check for any changes to the joystick/gamepad state and emit events for
* them. Called after {@code glfwPollEvents}.
*/
void process() {

if (glfwJoystickIsGamepad(GLFW_JOYSTICK_1)) {
glfwGetGamepadState(GLFW_JOYSTICK_1, gamepadState)

var axes = gamepadState.axes()
processAxis(axes, GLFW_GAMEPAD_AXIS_LEFT_X, 'Gamepad left stick X: {}')
processAxis(axes, GLFW_GAMEPAD_AXIS_LEFT_Y, 'Gamepad left stick Y: {}')
processAxis(axes, GLFW_GAMEPAD_AXIS_RIGHT_X, 'Gamepad right stick X: {}')
processAxis(axes, GLFW_GAMEPAD_AXIS_RIGHT_Y, 'Gamepad right stick Y: {}')
}
}

/**
* Process a single axis.
*/
private void processAxis(FloatBuffer axes, int type, String logMessage) {

var value = axes.get(type)
logger.debug(logMessage, sprintf('%.2f', value))
inputEventStream.trigger(new GamepadAxisEvent(type, value <= -0.2f || 0.2f <= value ? value : 0f))
}
}
Loading

0 comments on commit 28f8b1e

Please sign in to comment.