Skip to content

Commit

Permalink
Version 1.0.1, first actual release of the library, full world implem…
Browse files Browse the repository at this point in the history
…entation with custom world object support.
  • Loading branch information
KleeSup committed Oct 22, 2023
1 parent efc4f63 commit 8cb3a6c
Show file tree
Hide file tree
Showing 16 changed files with 691 additions and 94 deletions.
38 changes: 25 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,37 @@
- high velocity collision detection
- fixed tunneling problem
- detailed output with hit-position, normal, hit-time, etc.
- chunked world implementation and management

![Alt Text](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExYmZiNjdmYThmNDZmYzM0NzE2NDUyZmNlY2JlMzdhNTg0YzU2ZDFhMCZlcD12MV9pbnRlcm5hbF9naWZzX2dpZklkJmN0PWc/XrHcgxio3xjnXBAcb5/giphy.gif)

## How to use

```java
//create two AABBs, you can either use SimpleAABB for standalone or RectangleAABB which uses Rectangle from LibGDX
AABB player = new SimpleAABB(0,0,2,2);
AABB collider = new SimpleAABB(4,0,3,3);

//now we define the goal where the player wants to move at.
//NOTE: always calculate movements based of the CENTER of the AABB as this is where the library calculates collisions.
//in this example, we move by +10 units on the x-axis.
float goalX = player.getCenterX() + 10;
float goalY = player.getCenterY();

//now we can get our result. It contains various information of our collision.
SweptResult result = KleeSweptDetection.checkAABBvsAABB(player, collider, goalX, goalY);
System.out.println(result.isHit);
//create a new world instance with a fixed chunk size
SimpleCollisionWorld<SweptAABB> world = new SimpleCollisionWorld<>(chunkSize);

//initialize world objects
SweptAABB player = new SweptAABB();
SweptAABB obstacle = new SweptAABB();

//add them to the world with their bounding box
world.addBody(player, new Rectangle(0,0,10,10));
world.addBody(obstacle, new Rectangle(5,0,10,10));

//retrieving a world objects bounding box
Rectangle playerBoundingBox = world.getBoundingBox(player);
Rectangle obstacleBoundingBox = world.getBoundingBox(obstacle);

CollisionResponse response;
Vector2 displacement = new Vector2(1, 2);
//updating moves the body in the world to the best possible position and retrieves a collision response
response = world.update(player, displacement);
//simulating doesn't actually move the body in the world but still checks for collisions
response = world.simulate(player, displacement);
//forceUpdate forces a teleport/resize in the world (no checks for collisions)
world.forceUpdate(player, playerBoundingBox.x + displacement.x, playerBoundingBox.y + displacement.y);

```

## Implementation
Expand Down
13 changes: 3 additions & 10 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,11 @@ if (JavaVersion.current().isJava8Compatible()) {
compileJava {
// Targeting Java 7 is the lowest version you could need to go at this point.
// libGDX itself targets a mix of Java 7 (for most backends) and 8 (for LWJGL3).
if(JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_20)){
sourceCompatibility = 8
targetCompatibility = 8
sourceCompatibility = 8
targetCompatibility = 8
if (JavaVersion.current().isJava9Compatible()) {
options.release.set(8)
}
else {
sourceCompatibility = 7
targetCompatibility = 7
if (JavaVersion.current().isJava9Compatible()) {
options.release.set(7)
}
}
}

compileTestJava {
Expand Down
1 change: 0 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ POM_LICENCE_DIST=repo
# library issue; still, it isn't preferable to Apache, EPL, MIT, or BSD in regard
# to license compatibility with existing Java libraries.

# Obviously, change this part of the template if you aren't Tommy Ettinger.
POM_DEVELOPER_ID=KleeSup
POM_DEVELOPER_NAME=Ben L.
POM_DEVELOPER_URL=https://github.com/kleesup/
Expand Down
30 changes: 29 additions & 1 deletion src/main/java/com/github/kleesup/kleeswept/KleeHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Simple utility class.
* <br>Created on 17.04.2023</br>
* @author KleeSup
* @version 1.1
* @version 1.2
* @since 1.0.0
*/
public final class KleeHelper {
Expand Down Expand Up @@ -65,4 +65,32 @@ public static Rectangle calculateSumAABB(Rectangle aabb, Rectangle other){
return calculateSumAABB(aabb, other, null);
}

/**
* Pairs two integers into a long.
* @param x The first integer of the pair.
* @param y The second integer of the pair.
* @return The pair stored into a long.
*/
public static long pairLong(int x, int y){
return ((x & 0xFFFFFFFFL) | (y & 0xFFFFFFFFL) << 32);
}

/**
* Retrieves the first integer of a pair.
* @param pair The long where the integers were paired in.
* @return The first integer if a pair.
*/
public static int unpairIntY(long pair) {
return (int) (pair >>> 32);
}

/**
* Retrieves the second integer of a pair.
* @param pair The long where the integers were paired in.
* @return The second integer if a pair.
*/
public static int unpairIntX(long pair) {
return (int) pair;
}

}
75 changes: 32 additions & 43 deletions src/main/java/com/github/kleesup/kleeswept/KleeSweptDetection.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.github.kleesup.kleeswept.util.Single;

import java.util.*;

Expand Down Expand Up @@ -50,7 +51,7 @@ public static float getMaxY(Rectangle aabb){
public static boolean doesRayIntersectAABB(float x, float y, float goalX, float goalY, Rectangle aabb, Vector2 hitPosition, Vector2 normal, Vector2 tempMagWriteTo){
Vector2 magnitude = tempMagWriteTo != null ? tempMagWriteTo : new Vector2();
KleeHelper.calculateMagnitude(x,y,goalX,goalY,magnitude);
return doesRayIntersectAABB(x,y,magnitude,aabb,hitPosition,normal);
return doesRayIntersectAABB(x,y,magnitude,aabb,hitPosition,normal,null);
}
public static boolean doesRayIntersectAABB(float x, float y, float goalX, float goalY, Rectangle aabb, Vector2 hitPosition, Vector2 normal){
return doesRayIntersectAABB(x,y,goalX,goalY,aabb,hitPosition, normal, null);
Expand All @@ -66,7 +67,7 @@ public static boolean doesRayIntersectAABB(float x, float y, float goalX, float
* @param normal Required to write the ray-hit normal.
* @return Whether the ray hit the AABB.
*/
public static boolean doesRayIntersectAABB(float x, float y, Vector2 magnitude, Rectangle aabb, Vector2 hitPosition, Vector2 normal){
public static boolean doesRayIntersectAABB(float x, float y, Vector2 magnitude, Rectangle aabb, Vector2 hitPosition, Vector2 normal, Single<Float> outHitTime){
KleeHelper.paramRequireNonNull(magnitude, "Magnitude cannot be null!");
KleeHelper.paramRequireNonNull(aabb, "AABB cannot be null!");

Expand Down Expand Up @@ -94,21 +95,25 @@ public static boolean doesRayIntersectAABB(float x, float y, Vector2 magnitude,
//condition for a collision
if(firstExit > lastEntry && firstExit > 0 && lastEntry < 1){
if(hitPosition == null)hitPosition = new Vector2();
if(normal == null)normal = new Vector2();
normal.setZero();
hitPosition.set(x + magnitude.x * lastEntry, y + magnitude.y * lastEntry);

//calculating hit normal
float dx = hitPosition.x - getCenterX(aabb);
float dy = hitPosition.y - getCenterY(aabb);
float px = (aabb.getWidth()/2) - Math.abs(dx);
float py = (aabb.getHeight()/2) - Math.abs(dy);
if(outHitTime != null)outHitTime.set(lastEntry);

//calculating hit normal
if(px < py){
normal.x = (dx > 0 ? 1 : 0) - (dx < 0 ? 1 : 0);
}else{
normal.y = (dy > 0 ? 1 : 0) - (dy < 0 ? 1 : 0);
if(normal != null){
normal.setZero();

float dx = hitPosition.x - getCenterX(aabb);
float dy = hitPosition.y - getCenterY(aabb);
float px = (aabb.getWidth()/2) - Math.abs(dx);
float py = (aabb.getHeight()/2) - Math.abs(dy);

//calculating hit normal
if(px < py){
normal.x = (dx > 0 ? 1 : 0) - (dx < 0 ? 1 : 0);
}else{
normal.y = (dy > 0 ? 1 : 0) - (dy < 0 ? 1 : 0);
}
}
return true;
}
Expand All @@ -127,32 +132,21 @@ public static boolean doesRayIntersectAABB(float x, float y, Vector2 magnitude,
* @param displacement The displacement of the dynamic aabb.
* @param normal Required to write the hit normal.
* @param tempSum A temporary rectangle object where the summed AABB can be written into (Useful when less object creation is desired).
* @param tempMag A temporary vector object where the magnitude can be written into (Useful when less object creation is desired).
* @param tempRayHit A temporary vector object where the ray-hit result can be written into (Useful when less object creation is desired).
* @return {@code true} if the dynamic aabb collides with the static aabb, {@code false} otherwise.
*/
public static boolean checkDynamicVsStatic(Rectangle dynamicBox, Rectangle staticBox, Vector2 displacement, Vector2 normal,
Rectangle tempSum, Vector2 tempMag, Vector2 tempRayHit){
Rectangle tempSum, Vector2 tempRayHit, Single<Float> outHitTime){
KleeHelper.paramRequireNonNull(dynamicBox, "Dynamic AABB cannot be null!");
KleeHelper.paramRequireNonNull(staticBox, "Static AABB cannot be null!");
if(displacement == null)displacement = new Vector2();
KleeHelper.calculateSumAABB(dynamicBox, staticBox, tempSum);
float goalX = getCenterX(dynamicBox) + displacement.x;
float goalY = getCenterY(dynamicBox) + displacement.y;

normal = normal == null ? new Vector2() : normal.setZero();
if(tempRayHit == null)tempRayHit = new Vector2();
if(doesRayIntersectAABB(getCenterX(dynamicBox), getCenterY(dynamicBox), goalX, goalY, staticBox, tempRayHit, normal, tempMag)){
//resolution
float penetrationX = normal.x != 0 ? goalX - tempRayHit.x : 0;
float penetrationY = normal.y != 0 ? goalY - tempRayHit.y : 0;
displacement.sub(penetrationX, penetrationY);
return true;
}
return false;
return doesRayIntersectAABB(getCenterX(dynamicBox), getCenterY(dynamicBox), displacement, tempSum, tempRayHit, normal, outHitTime);
}
public static boolean checkDynamicVsStatic(Rectangle dynamicBox, Rectangle staticBox, Vector2 displacement, Vector2 normal){
return checkDynamicVsStatic(dynamicBox, staticBox, displacement, normal, null, null, null);
return checkDynamicVsStatic(dynamicBox, staticBox, displacement, normal, null, null,null);
}

/**
Expand All @@ -162,29 +156,27 @@ public static boolean checkDynamicVsStatic(Rectangle dynamicBox, Rectangle stati
* @param displacement The displacement of the dynamic aabb.
* @param tempNormal A temporary vector object where the hit-normal can be written into (Useful when less object creation is desired).
* @param tempSum A temporary rectangle object where the summed AABB can be written into (Useful when less object creation is desired).
* @param tempMag A temporary vector object where the magnitude can be written into (Useful when less object creation is desired).
* @param tempRayHit A temporary vector object where the ray-hit result can be written into (Useful when less object creation is desired).
* @return A collection of all the static rectangles that are hit.
*/
public static Collection<Rectangle> checkDynamicVsMultipleStatic(Rectangle dynamicBox, Collection<Rectangle> staticBoxes, Vector2 displacement, Vector2 tempNormal,
Rectangle tempSum, Vector2 tempMag, Vector2 tempRayHit){
Rectangle tempSum, Vector2 tempRayHit){
KleeHelper.paramRequireNonNull(dynamicBox, "Dynamic AABB cannot be null");
if(staticBoxes == null || staticBoxes.isEmpty())return staticBoxes != null ? staticBoxes : new ArrayList<Rectangle>();
if(staticBoxes == null || staticBoxes.isEmpty())return staticBoxes != null ? staticBoxes : new ArrayList<>();
if(tempNormal == null)tempNormal = new Vector2();
if(tempSum == null)tempSum = new Rectangle();
if(tempMag == null)tempMag = new Vector2();
if(tempRayHit == null)tempRayHit = new Vector2();
Iterator<Rectangle> itr = staticBoxes.iterator();
while (itr.hasNext()){
Rectangle staticBox = itr.next();
if(!checkDynamicVsStatic(dynamicBox, staticBox, displacement, tempNormal, tempSum, tempMag, tempRayHit)){
if(!checkDynamicVsStatic(dynamicBox, staticBox, displacement, tempNormal, tempSum, tempRayHit, null)){
itr.remove();
}
}
return staticBoxes;
}
public static Collection<Rectangle> checkDynamicVsMultipleStatic(Rectangle dynamicBox, Collection<Rectangle> staticBoxes, Vector2 displacement){
return checkDynamicVsMultipleStatic(dynamicBox, staticBoxes, displacement, null, null, null, null);
return checkDynamicVsMultipleStatic(dynamicBox, staticBoxes, displacement, null, null, null);
}

/*
Expand All @@ -201,17 +193,16 @@ public static Collection<Rectangle> checkDynamicVsMultipleStatic(Rectangle dynam
* @param secondDisplacement The displacement of the second dynamic aabb.
* @param normal Required to write the hit normal.
* @param tempSum A temporary rectangle object where the summed AABB can be written into (Useful when less object creation is desired).
* @param tempMag A temporary vector object where the magnitude can be written into (Useful when less object creation is desired).
* @param tempRayHit A temporary vector object where the ray-hit result can be written into (Useful when less object creation is desired).
* @return {@code true} if the both AABBs collide with each other, {@code false} otherwise.
*/
public static boolean checkDynamicVsDynamic(Rectangle firstBox, Vector2 firstDisplacement, Rectangle secondBox, Vector2 secondDisplacement, Vector2 normal,
Rectangle tempSum, Vector2 tempMag, Vector2 tempRayHit){
Rectangle tempSum, Vector2 tempRayHit){
firstDisplacement.set(firstDisplacement.x - secondDisplacement.x, firstDisplacement.y - secondDisplacement.y);
return checkDynamicVsStatic(firstBox, secondBox, firstDisplacement, normal, tempSum, tempMag, tempRayHit);
return checkDynamicVsStatic(firstBox, secondBox, firstDisplacement, normal, tempSum, tempRayHit, null);
}
public static boolean checkDynamicVsDynamic(Rectangle firstBox, Vector2 firstDisplacement, Rectangle secondBox, Vector2 secondDisplacement, Vector2 normal){
return checkDynamicVsDynamic(firstBox, firstDisplacement, secondBox, secondDisplacement, normal, null, null, null);
return checkDynamicVsDynamic(firstBox, firstDisplacement, secondBox, secondDisplacement, normal, null, null);
}

/**
Expand All @@ -221,29 +212,27 @@ public static boolean checkDynamicVsDynamic(Rectangle firstBox, Vector2 firstDis
* @param otherBoxes The collection of dynamic aabbs with their displacement.
* @param tempNormal A temporary vector object where the hit-normal can be written into (Useful when less object creation is desired).
* @param tempSum A temporary rectangle object where the summed AABB can be written into (Useful when less object creation is desired).
* @param tempMag A temporary vector object where the magnitude can be written into (Useful when less object creation is desired).
* @param tempRayHit A temporary vector object where the ray-hit result can be written into (Useful when less object creation is desired).
* @return A map containing all the dynamic aabbs that were hit (with their displacements).
*/
public static Map<Rectangle, Vector2> checkDynamicVsMultipleDynamic(Rectangle firstBox, Vector2 firstDisplacement, Map<Rectangle, Vector2> otherBoxes, Vector2 tempNormal,
Rectangle tempSum, Vector2 tempMag, Vector2 tempRayHit){
Rectangle tempSum, Vector2 tempRayHit){
KleeHelper.paramRequireNonNull(firstBox, "Dynamic AABB cannot be null");
if(otherBoxes == null || otherBoxes.isEmpty())return otherBoxes != null ? otherBoxes : new HashMap<Rectangle, Vector2>();
if(otherBoxes == null || otherBoxes.isEmpty())return otherBoxes != null ? otherBoxes : new HashMap<>();
if(tempNormal == null)tempNormal = new Vector2();
if(tempSum == null)tempSum = new Rectangle();
if(tempMag == null)tempMag = new Vector2();
if(tempRayHit == null)tempRayHit = new Vector2();
Iterator<Map.Entry<Rectangle, Vector2>> itr = otherBoxes.entrySet().iterator();
while (itr.hasNext()){
Map.Entry<Rectangle, Vector2> entry = itr.next();
if(!checkDynamicVsDynamic(firstBox, firstDisplacement, entry.getKey(), entry.getValue(), tempNormal, tempSum, tempMag, tempRayHit)){
if(!checkDynamicVsDynamic(firstBox, firstDisplacement, entry.getKey(), entry.getValue(), tempNormal, tempSum, tempRayHit)){
itr.remove();
}
}
return otherBoxes;
}
public static Map<Rectangle, Vector2> checkDynamicVsMultipleDynamic(Rectangle firstBox, Vector2 firstDisplacement, Map<Rectangle, Vector2> otherBoxes){
return checkDynamicVsMultipleDynamic(firstBox, firstDisplacement, otherBoxes, null, null, null, null);
return checkDynamicVsMultipleDynamic(firstBox, firstDisplacement, otherBoxes, null, null, null);
}


Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/github/kleesup/kleeswept/util/Single.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.github.kleesup.kleeswept.util;

/**
* <br>Created on 18.10.2023</br>
* @author KleeSup
* @version 1.0.1
* @since 1.0
*/
public class Single<T> {

private T obj;
public Single(T initialValue){
this.obj = initialValue;
}

public T get() {
return obj;
}

public void set(T t) {
this.obj = t;
}
}
Loading

0 comments on commit 8cb3a6c

Please sign in to comment.