This is a collection of little snippets or classes that may be useful at some point when working with LibGDX.
Last active
January 1, 2023 14:17
-
-
Save Nithanim/46e0ca64192cb7d23ed02d2ac187c259 to your computer and use it in GitHub Desktop.
LibGDX Helpers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.mygdx.game; | |
import java.util.EnumMap; | |
import java.util.EnumSet; | |
import java.util.Set; | |
import com.badlogic.gdx.Gdx; | |
import com.badlogic.gdx.Input.Buttons; | |
import com.badlogic.gdx.Input.Keys; | |
import com.badlogic.gdx.graphics.Camera; | |
import com.badlogic.gdx.input.GestureDetector; | |
import com.badlogic.gdx.math.MathUtils; | |
import com.badlogic.gdx.math.Vector2; | |
import com.badlogic.gdx.math.Vector3; | |
import com.badlogic.gdx.utils.IntMap; | |
/** | |
It is a modified, stripped and (codewise) simplified version of {@link com.badlogic.gdx.graphics.g3d.utils.CameraInputController}. | |
Their license applies! | |
All very weird controls have been fixed and now behaves like you would expect from a free-cam. | |
Additionally, the code is MUCH easier to understand and build upon now. But it might be a little bit worse performance-wise at some points. | |
*/ | |
public class FreeCam extends GestureDetector { | |
/** The button for rotating the camera. */ | |
public int rotateButton = Buttons.RIGHT; | |
/** The angle to rotate when moved the full width or height of the screen. */ | |
public float rotateAngle = 360f; | |
/** The button for translating the camera along the up/right plane */ | |
public int translateButton = Buttons.LEFT; | |
/** The units to translate the camera when moved the full width or height of the screen. */ | |
public float translateUnits = 40f; | |
/** The button for translating the camera along the direction axis */ | |
public int forwardButton = Buttons.MIDDLE; | |
/** | |
* The key which must be pressed to activate rotate, translate and forward or 0 to always | |
* activate. | |
*/ | |
public int activateKey = 1; | |
private final Set<KeyboardAction> keyboardPressedKey = EnumSet.noneOf(KeyboardAction.class); | |
/** Indicates if the activateKey is currently being pressed. */ | |
protected boolean activatePressed; | |
/** | |
* Whether scrolling requires the activeKey to be pressed (false) or always allow scrolling | |
* (true). | |
*/ | |
public boolean alwaysScroll = true; | |
/** The weight for each scrolled amount. */ | |
public float scrollFactor = -0.1f; | |
/** World units per screen size */ | |
public float pinchZoomFactor = 10f; | |
/** Whether to update the camera after it has been changed. */ | |
public boolean autoUpdate = true; | |
private final IntMap<KeyboardAction> keyboardKeyAssignments = new IntMap<>(); | |
{ | |
keyboardKeyAssignments.put(Keys.W, KeyboardAction.FORWARD); | |
keyboardKeyAssignments.put(Keys.S, KeyboardAction.BACKWARD); | |
keyboardKeyAssignments.put(Keys.A, KeyboardAction.LEFT); | |
keyboardKeyAssignments.put(Keys.D, KeyboardAction.RIGHT); | |
} | |
public Camera camera; | |
/** The current (first) button being pressed. */ | |
protected int button = -1; | |
private int dragStartX, dragStartY; | |
private final Vector3 tmp1 = new Vector3(); | |
private final Vector3 tmp2 = new Vector3(); | |
protected static class CameraGestureListener extends GestureAdapter { | |
public FreeCam controller; | |
private float previousZoom; | |
@Override | |
public boolean touchDown(float x, float y, int pointer, int button) { | |
previousZoom = 0; | |
return false; | |
} | |
@Override | |
public boolean tap(float x, float y, int count, int button) { | |
return false; | |
} | |
@Override | |
public boolean longPress(float x, float y) { | |
return false; | |
} | |
@Override | |
public boolean fling(float velocityX, float velocityY, int button) { | |
return false; | |
} | |
@Override | |
public boolean pan(float x, float y, float deltaX, float deltaY) { | |
return false; | |
} | |
@Override | |
public boolean zoom(float initialDistance, float distance) { | |
float newZoom = distance - initialDistance; | |
float amount = newZoom - previousZoom; | |
previousZoom = newZoom; | |
float w = Gdx.graphics.getWidth(), h = Gdx.graphics.getHeight(); | |
return controller.pinchZoom(amount / Math.min(w, h)); | |
} | |
@Override | |
public boolean pinch( | |
Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2) { | |
return false; | |
} | |
} | |
protected final CameraGestureListener gestureListener; | |
protected FreeCam(final CameraGestureListener gestureListener, final Camera camera) { | |
super(gestureListener); | |
this.gestureListener = gestureListener; | |
this.gestureListener.controller = this; | |
this.camera = camera; | |
} | |
public FreeCam(final Camera camera) { | |
this(new CameraGestureListener(), camera); | |
} | |
public void update() { | |
if (!keyboardPressedKey.isEmpty()) { | |
final float delta = Gdx.graphics.getDeltaTime(); | |
for (KeyboardAction keyboardAction : keyboardPressedKey) { | |
keyboardActionHandlers.get(keyboardAction).onAction(delta); | |
} | |
if (autoUpdate) { | |
camera.update(); | |
} | |
} | |
} | |
private final EnumMap<KeyboardAction, KeyboardActionHandler> keyboardActionHandlers = | |
new EnumMap<>(KeyboardAction.class); | |
{ | |
keyboardActionHandlers.put( | |
KeyboardAction.FORWARD, | |
(delta) -> camera.translate(tmp1.set(camera.direction).scl(delta * translateUnits))); | |
keyboardActionHandlers.put( | |
KeyboardAction.BACKWARD, | |
(delta) -> camera.translate(tmp1.set(camera.direction).scl(-delta * translateUnits))); | |
keyboardActionHandlers.put( | |
KeyboardAction.LEFT, | |
(delta) -> | |
camera.translate( | |
tmp1.set(camera.up).crs(camera.direction).nor().scl(delta * translateUnits))); | |
keyboardActionHandlers.put( | |
KeyboardAction.RIGHT, | |
(delta) -> | |
camera.translate( | |
tmp1.set(camera.direction).crs(camera.up).nor().scl(delta * translateUnits))); | |
} | |
@FunctionalInterface | |
private interface KeyboardActionHandler { | |
void onAction(float timeDelta); | |
} | |
private int touched; | |
private boolean multiTouch; | |
@Override | |
public boolean touchDown(int screenX, int screenY, int pointer, int button) { | |
touched |= (1 << pointer); | |
multiTouch = !MathUtils.isPowerOfTwo(touched); | |
if (multiTouch) { | |
this.button = -1; | |
} else if (this.button < 0 && (activateKey == button || activatePressed)) { | |
dragStartX = screenX; | |
dragStartY = screenY; | |
this.button = button; | |
Gdx.input.setCursorCatched(true); | |
} | |
return super.touchDown(screenX, screenY, pointer, button) | |
|| (activateKey == 0 || activatePressed); | |
} | |
@Override | |
public boolean touchUp(int screenX, int screenY, int pointer, int button) { | |
touched &= ~(1 << pointer); | |
multiTouch = !MathUtils.isPowerOfTwo(touched); | |
if (button == this.button) { | |
this.button = -1; | |
} | |
Gdx.input.setCursorCatched(false); | |
return super.touchUp(screenX, screenY, pointer, button) || activatePressed; | |
} | |
protected boolean process(float deltaX, float deltaY, int button) { | |
if (button == rotateButton) { | |
tmp1.set(camera.direction).crs(camera.up).y = 0f; | |
tmp1.nor(); | |
camera.rotate(tmp1, deltaY * rotateAngle); | |
camera.rotate(Vector3.Y, deltaX * -rotateAngle); | |
} else if (button == translateButton) { | |
camera.translate( | |
tmp1.set(camera.direction).crs(camera.up).nor().scl(-deltaX * translateUnits)); | |
camera.translate(tmp2.set(camera.up).scl(-deltaY * translateUnits)); | |
} else if (button == forwardButton) { | |
camera.translate(tmp1.set(camera.direction).scl(deltaY * translateUnits)); | |
} | |
if (autoUpdate) camera.update(); | |
return true; | |
} | |
@Override | |
public boolean touchDragged(int screenX, int screenY, int pointer) { | |
boolean result = super.touchDragged(screenX, screenY, pointer); | |
if (result || this.button < 0) return result; | |
final float deltaX = (screenX * 1f - dragStartX) / Gdx.graphics.getWidth(); | |
final float deltaY = (dragStartY * 1f - screenY) / Gdx.graphics.getHeight(); | |
dragStartX = screenX; | |
dragStartY = screenY; | |
return process(deltaX, deltaY, button); | |
} | |
@Override | |
public boolean scrolled(float amountX, float amountY) { | |
return zoom(amountY * scrollFactor * translateUnits); | |
} | |
public boolean zoom(float amount) { | |
if (!alwaysScroll && activateKey != 0 && !activatePressed) return false; | |
camera.translate(tmp1.set(camera.direction).scl(amount)); | |
if (autoUpdate) camera.update(); | |
return true; | |
} | |
protected boolean pinchZoom(float amount) { | |
return zoom(pinchZoomFactor * amount); | |
} | |
@Override | |
public boolean keyDown(int keycode) { | |
if (keycode == activateKey) { | |
activatePressed = true; | |
} | |
var action = keyboardKeyAssignments.get(keycode); | |
if (action != null) { | |
keyboardPressedKey.add(action); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
@Override | |
public boolean keyUp(int keycode) { | |
if (keycode == activateKey) { | |
activatePressed = false; | |
button = -1; | |
} | |
var action = keyboardKeyAssignments.get(keycode); | |
if (action != null) { | |
keyboardPressedKey.remove(action); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
private enum KeyboardAction { | |
FORWARD, | |
BACKWARD, | |
LEFT, | |
RIGHT | |
} | |
private enum MouseAction { | |
ROTATE | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
Raycast starting from the clickeed pixel on screen. The arrow "directionArrow" is for vizualisatzion in the 3d world. | |
Thanks to | |
https://antongerdelan.net/opengl/raycasting.html | |
https://permadi.com/1996/05/ray-casting-tutorial-5/ | |
for helping me out a little with some specifics! | |
*/ | |
InputProcessor ip = | |
new InputAdapter() { | |
@Override | |
public boolean touchDown(int screenX, int screenY, int pointer, int button) { | |
if (button == 0) { | |
// Convert the window coordinates to the normalized device coordinates (-1 to 1) | |
float x = ((float) screenX / Gdx.graphics.getWidth()) * 2.0f - 1.0f; | |
float y = 1.0f - ((float) screenY / Gdx.graphics.getHeight()) * 2.0f; | |
float z = -1.0f; | |
// The camera matrices translate the world coordinates to the normalized device coordinates. | |
// Using the inverted matrix reverses the calcultion. | |
var pointProjectionPlane = new Vector3(x, y, z).mul(cam.invProjectionView); | |
// The direction of the ray is determined by the camera pos to the point on the projection plane. | |
Vector3 direction = pointProjectionPlane.cpy().sub(cam.position).nor(); | |
Ray ray = new Ray(pointProjectionPlane, direction); | |
{ | |
// Visulization of the correcty ray by literally shooting it out of "the screen" (ratheer our projection plane). | |
ModelBuilder modelBuilder = new ModelBuilder(); | |
var model = | |
modelBuilder.createArrow( | |
ray.origin, // Effectively: pointProjectionPlane | |
ray.getEndPoint(new Vector3(), 5), // Effectively: pointProjectionPlane + direction * 5 | |
new Material(), | |
VertexAttributes.Usage.Position | VertexAttributes.Usage.ColorPacked); | |
if (directionArrow != null) { | |
directionArrow.model.dispose(); | |
} | |
directionArrow = new ModelInstance(model); | |
} | |
return true; | |
} else { | |
return false; | |
} | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment