Last active
August 2, 2020 19:18
-
-
Save lyze237/9a8698d27e985e0858d0700c883c062f to your computer and use it in GitHub Desktop.
Ball/Projectile/Nades/... reflection/bounce off walls
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 dev.lyze.reflection; | |
import com.badlogic.gdx.math.Vector2; | |
public class Projectile { | |
private Vector2 pos; | |
private Vector2 dir; | |
public Projectile(Vector2 pos, Vector2 dir) { | |
this.pos = pos; | |
this.dir = dir.scl(0.05f); | |
} | |
public float getX() { | |
return pos.x; | |
} | |
public void setX(float x) { | |
this.pos.x = x; | |
} | |
public float getY() { | |
return pos.y; | |
} | |
public void setY(float y) { | |
this.pos.y = y; | |
} | |
public float getDx() { | |
return dir.x; | |
} | |
public void setDx(float dx) { | |
this.dir.x = dx; | |
} | |
public float getDy() { | |
return dir.y; | |
} | |
public void setDy(float dy) { | |
this.dir.y = dy; | |
} | |
public Vector2 getPos() { | |
return pos; | |
} | |
public void setPos(Vector2 pos) { | |
this.pos = pos; | |
} | |
public Vector2 getDir() { | |
return dir; | |
} | |
public void setDir(Vector2 dir) { | |
this.dir = dir; | |
} | |
@Override | |
public String toString() { | |
return "Projectile{" + | |
"x=" + getX() + | |
", y=" + getY() + | |
", dx=" + getDx() + | |
", dy=" + getDy() + | |
'}'; | |
} | |
} |
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 dev.lyze.reflection; | |
import com.badlogic.gdx.ApplicationAdapter; | |
import com.badlogic.gdx.Gdx; | |
import com.badlogic.gdx.Input; | |
import com.badlogic.gdx.graphics.Color; | |
import com.badlogic.gdx.graphics.GL30; | |
import com.badlogic.gdx.graphics.OrthographicCamera; | |
import com.badlogic.gdx.graphics.glutils.ShapeRenderer; | |
import com.badlogic.gdx.math.Intersector; | |
import com.badlogic.gdx.math.Rectangle; | |
import com.badlogic.gdx.math.Vector2; | |
import com.badlogic.gdx.utils.viewport.ScreenViewport; | |
import java.util.ArrayList; | |
public class ReflectionTests extends ApplicationAdapter { | |
private ShapeRenderer shapeRenderer; | |
private ScreenViewport screenViewport; | |
private final ArrayList<Projectile> projectiles = new ArrayList<>(); | |
private final ArrayList<Wall> walls = new ArrayList<>(); | |
private final Vector2 projectileCalculatedPosition = new Vector2(); | |
private final Vector2 normalVector1 = new Vector2(), normalVector2 = new Vector2(); | |
private final Vector2 endpointVector1 = new Vector2(), endpointVector2 = new Vector2(); | |
private final Vector2 collisionPoint = new Vector2(); | |
private final Vector2 spawnNewProjectilePosition = new Vector2(); | |
private boolean spawnNewProjectile; | |
private final Vector2 spawnNewWallPosition = new Vector2(); | |
private boolean spawnNewWall; | |
private final Rectangle windowRectangle = new Rectangle(); | |
@Override | |
public void create() { | |
shapeRenderer = new ShapeRenderer(); | |
var orthographicCamera = new OrthographicCamera(); | |
orthographicCamera.setToOrtho(true); | |
screenViewport = new ScreenViewport(orthographicCamera); | |
} | |
@Override | |
public void render() { | |
Gdx.gl.glClearColor(0.2f, 0.2f, 0.2f, 1); | |
Gdx.gl.glClear(GL30.GL_COLOR_BUFFER_BIT); | |
screenViewport.apply(); | |
shapeRenderer.setProjectionMatrix(screenViewport.getCamera().combined); | |
shapeRenderer.begin(ShapeRenderer.ShapeType.Line); | |
checkSpawnProjectile(); | |
checkSpawnWall(); | |
update(); | |
drawEverything(); | |
shapeRenderer.end(); | |
} | |
// press left click to set position of new projectile | |
// press left click again to set direction vector of projectile relative to whatever the mouse cursor moved | |
private void checkSpawnProjectile() { | |
if (Gdx.input.isButtonJustPressed(Input.Buttons.LEFT)) { | |
if (!spawnNewProjectile) { | |
spawnNewProjectilePosition.set(Gdx.input.getX(), Gdx.input.getY()); | |
spawnNewProjectile = true; | |
} | |
else { | |
projectiles.add(new Projectile(new Vector2(spawnNewProjectilePosition.x, spawnNewProjectilePosition.y), new Vector2(Gdx.input.getX() - spawnNewProjectilePosition.x, Gdx.input.getY() - spawnNewProjectilePosition.y))); | |
spawnNewProjectile = false; | |
} | |
} | |
if (spawnNewProjectile) { | |
shapeRenderer.setColor(Color.BROWN); | |
shapeRenderer.line(spawnNewProjectilePosition.x, spawnNewProjectilePosition.y, Gdx.input.getX(), Gdx.input.getY()); | |
} | |
} | |
// press right click to set begin position of wall | |
// press right click again to set end position of wall and spawn it | |
private void checkSpawnWall() { | |
if (Gdx.input.isButtonJustPressed(Input.Buttons.RIGHT)) { | |
if (!spawnNewWall) { | |
spawnNewWallPosition.set(Gdx.input.getX(), Gdx.input.getY()); | |
spawnNewWall = true; | |
} | |
else { | |
walls.add(new Wall(new Vector2(spawnNewWallPosition.x, spawnNewWallPosition.y), new Vector2(Gdx.input.getX(), Gdx.input.getY()))); | |
spawnNewWall = false; | |
} | |
} | |
if (spawnNewWall) { | |
shapeRenderer.setColor(Color.FIREBRICK); | |
shapeRenderer.line(spawnNewWallPosition.x, spawnNewWallPosition.y, Gdx.input.getX(), Gdx.input.getY()); | |
} | |
} | |
private void update() { | |
for (int i = projectiles.size() - 1; i >= 0; i--) { | |
Projectile p = projectiles.get(i); | |
var hit = false; | |
for (Wall wall : walls) | |
hit |= checkProjectileHitWall(p, wall); | |
if (!hit) | |
p.getPos().add(p.getDir()); | |
// remove out of bounds projectiles | |
if (!windowRectangle.contains(p.getPos())) | |
projectiles.remove(i); | |
} | |
} | |
private void drawEverything() { | |
shapeRenderer.setColor(Color.GREEN); | |
walls.forEach(wall -> shapeRenderer.line(wall.getBegin(), wall.getEnd())); | |
for (Projectile p: projectiles) { | |
shapeRenderer.setColor(Color.BLUE); | |
shapeRenderer.circle(p.getX(), p.getY(), 5); | |
shapeRenderer.setColor(Color.RED); | |
shapeRenderer.line(p.getX(), p.getY(), p.getX() + p.getDx(), p.getY() + p.getDy()); | |
} | |
} | |
private boolean checkProjectileHitWall(Projectile p, Wall w) { | |
projectileCalculatedPosition.set(p.getX() + p.getDx(), p.getY() + p.getDy()); | |
// when projectile intersects with wall, bounce | |
if (Intersector.intersectSegments(w.getBegin(), w.getEnd(), p.getPos(), projectileCalculatedPosition, collisionPoint)) { | |
// calculate wall normals | |
var wallNx = w.getEnd().x - w.getBegin().x; | |
var wallNy = w.getEnd().y - w.getBegin().y; | |
normalVector1.set(-wallNy, wallNx); | |
normalVector2.set(wallNy, -wallNx); | |
// figure out which normal is the one we're looking for (=> closer to the projectile) | |
// len2() doesn't square the result, so it's more performant | |
endpointVector1.set(collisionPoint.x + normalVector1.x, collisionPoint.y + normalVector1.y); | |
endpointVector2.set(collisionPoint.x + normalVector2.x, collisionPoint.y + normalVector2.y); | |
var length1 = endpointVector1.sub(p.getPos()).len2(); | |
var length2 = endpointVector2.sub(p.getPos()).len2(); | |
var normalVector = length1 < length2 ? normalVector1 : normalVector2; | |
shapeRenderer.setColor(Color.PINK); | |
shapeRenderer.line(collisionPoint.x, collisionPoint.y, normalVector.x + collisionPoint.x, normalVector.y + collisionPoint.y); | |
// do magic https://stackoverflow.com/a/573206/8482314 | |
// kept variables instead of moving them together so it's easier recognizable with the SO answer | |
var v = p.getDir(); | |
var n = normalVector.nor(); | |
var u = n.scl(v.dot(n)); | |
var ww = v.sub(u); | |
var v2 = ww.sub(u); | |
p.setDir(v2); | |
return true; | |
} | |
shapeRenderer.setColor(Color.YELLOW); | |
shapeRenderer.circle(collisionPoint.x, collisionPoint.y, 5); | |
return false; | |
} | |
@Override | |
public void resize(int width, int height) { | |
screenViewport.update(width, height, true); | |
windowRectangle.set(0, 0, width,height); | |
} | |
} |
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 dev.lyze.reflection; | |
import com.badlogic.gdx.math.Vector2; | |
public class Wall { | |
private Vector2 begin, end; | |
public Wall(Vector2 begin, Vector2 end) { | |
this.begin = begin; | |
this.end = end; | |
} | |
public Vector2 getBegin() { | |
return begin; | |
} | |
public void setBegin(Vector2 begin) { | |
this.begin = begin; | |
} | |
public Vector2 getEnd() { | |
return end; | |
} | |
public void setEnd(Vector2 end) { | |
this.end = end; | |
} | |
@Override | |
public String toString() { | |
return "Wall{" + | |
"begin=" + begin + | |
", end=" + end + | |
'}'; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment