Skip to content

Commit

Permalink
fix: explosion now calculates entity exposure correctly. In previous …
Browse files Browse the repository at this point in the history
…version any non-air block will block the explosion ray
  • Loading branch information
smartcmd committed Jan 22, 2025
1 parent 6c17a09 commit dc360b6
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Unless otherwise specified, any version comparison below is the comparison of se

- (API) Implemented ender chest, and several related interfaces & objects including `BlockEntityEnderChest`,
`EnderChestContainer`, are added to api module, see commit history for more details.
- (API) Added `VoxelShape#intersectsRay` method, which can determine whether the given ray intersects the voxel shape.

### Changed

Expand All @@ -31,6 +32,7 @@ Unless otherwise specified, any version comparison below is the comparison of se

- Plugins are able to create their own world generator implementation now. In previous versions a ClassCastException would be thrown when
initializing the dimension.
- Explosion now calculates entity exposure correctly. In previous version any non-air block will block the explosion ray.

## [0.1.3](https://github.com/AllayMC/Allay/releases/tag/0.1.3) (API 0.4.0) - 2025-1-17

Expand Down
97 changes: 97 additions & 0 deletions api/src/main/java/org/allaymc/api/math/voxelshape/VoxelShape.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.allaymc.api.block.data.BlockFace;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.joml.Vector3fc;
import org.joml.primitives.AABBf;
import org.joml.primitives.AABBfc;
import org.joml.primitives.Rayf;

import java.util.*;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -286,6 +289,100 @@ public boolean isEdgeFull(BlockFace face) {
return true;
}

/**
* @see #intersectsRay(Vector3fc, Vector3fc)
*/
public boolean intersectsRay(float sx, float sy, float sz, float ex, float ey, float ez) {
return intersectsRay(new Vector3f(sx, sy, sz), new Vector3f(ex, ey, ez));
}

/**
* Determine whether the given ray intersects this voxel shape.
*
* @param start the start point of the ray.
* @param end the end point of the ray.
*
* @return {@code true} if the ray intersects this voxel shape, otherwise {@code false}.
*/
public boolean intersectsRay(Vector3fc start, Vector3fc end) {
var ray = new Rayf(start, end.sub(start, new Vector3f()));

if (vacancies.isEmpty()) {
// This would be quicker if no vacancy exists
return solids.stream().anyMatch(solid -> solid.intersectsRay(ray));
}

var solidIntervals = mergeIntervals(intersectsRay(solids, ray));
var vacancyIntervals = mergeIntervals(intersectsRay(vacancies, ray));

// For each solid intervals, check if they can be fully covered by vacancy intervals
for (var solidInterval : solidIntervals) {
float currentStart = solidInterval.x;
boolean isCovered = false;

for (var vacancyInterval : vacancyIntervals) {
// Check if vacancy interval cover the starting point of the currently uncovered part of solid interval
if (vacancyInterval.x <= currentStart && vacancyInterval.y >= currentStart) {
// Update the current starting point to the end point of the vacancy interval
currentStart = Math.max(currentStart, vacancyInterval.y);
// If the current starting point has exceeded the end point of the solid interval, it means it is completely covered.
if (currentStart >= solidInterval.y) {
isCovered = true;
break;
}
}
}

if (!isCovered) {
// Return false directly if any of the solid interval is not fully covered
return true;
}
}

return false;
}

private List<Vector2f> mergeIntervals(List<Vector2f> intervals) {
// Sort by interval starting point
intervals.sort((a, b) -> Float.compare(a.x, b.x));
List<Vector2f> merged = new ArrayList<>();

for (Vector2f interval : intervals) {
// If merged is empty or the current interval does not overlap with the previous merged interval
if (merged.isEmpty() || merged.getLast().y < interval.x) {
// Add current interval
merged.add(new Vector2f(interval));
} else {
// Merge interval
merged.getLast().y = Math.max(merged.getLast().y, interval.y);
}
}

return merged;
}

private List<Vector2f> intersectsRay(Set<AABBfc> aabbs, Rayf ray) {
var set = new ArrayList<Vector2f>();
for (var aabb : aabbs) {
var result = new Vector2f();
if (aabb.intersectsRay(ray, result)) {
set.add(order(result));
}
}

return set;
}

private Vector2f order(Vector2f vec) {
if (vec.x > vec.y) {
float temp = vec.x;
vec.x = vec.y;
vec.y = temp;
}

return vec;
}

private List<float[]> getEdgeRegions(BlockFace face, float edgeWidth) {
List<float[]> edgeRegions = new ArrayList<>();

Expand Down
8 changes: 4 additions & 4 deletions api/src/main/java/org/allaymc/api/world/Explosion.java
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,8 @@ protected float exposure(Dimension dimension, Vector3fc origin, AABBfc box) {
float xOffset = (1.0f - (float) Math.floor(diff.x()) / diff.x()) / 2.0f;
float zOffset = (1.0f - (float) Math.floor(diff.z()) / diff.z()) / 2.0f;

int checks = 0;
int misses = 0;
float checks = 0;
float misses = 0;
for (var x = 0.0f; x <= 1.0f; x += step.x()) {
for (var y = 0.0f; y <= 1.0f; y += step.y()) {
for (var z = 0.0f; z <= 1.0f; z += step.z()) {
Expand All @@ -340,7 +340,7 @@ protected float exposure(Dimension dimension, Vector3fc origin, AABBfc box) {
final boolean[] collided = new boolean[1];
traverseBlocks(origin, point, pos -> {
var block = dimension.getBlockState(pos);
if (block.getBlockType() != BlockTypes.AIR) {
if (block.getBlockStateData().computeOffsetCollisionShape(pos).intersectsRay(origin, point)) {
collided[0] = true;
return false;
}
Expand All @@ -357,7 +357,7 @@ protected float exposure(Dimension dimension, Vector3fc origin, AABBfc box) {
}
}

return ((float) misses) / ((float) checks);
return misses / checks;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,9 @@ public void prepareCommandTree(CommandTree tree) {
.exec((context, player) -> {
var explosion = new Explosion(context.getResult(2), context.getResult(3));
Vector3f pos = context.getResult(1);
if (pos == null) {
pos = new Vector3f(context.getSender().getCmdExecuteLocation());
}
explosion.explode(player.getDimension(), pos);
return context.success();
}, SenderType.PLAYER);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,62 @@ void testIsEdgeFull() {
.build();
assertFalse(vs5.isEdgeFull(BlockFace.UP));
}

@Test
void testIntersectsRay() {
var vs1 = VoxelShape
.builder()
.solid(0, 0, 0, 1, 1, 1)
.build();
assertTrue(vs1.intersectsRay(0, 0, 0, 1, 1, 1));

var vs2 = VoxelShape
.builder()
.solid(0, 0, 0, 1, 1, 1)
.vacancy(0.1f, 0.1f, 0.1f, 0.9f, 0.9f, 0.9f)
.build();
assertTrue(vs2.intersectsRay(0, 0, 0, 1, 1, 1));

var vs3 = VoxelShape
.builder()
.solid(0, 0, 0, 1, 1, 1)
.vacancy(0.4f, 0, 0.4f, 0.6f, 1, 0.6f)
.build();
assertFalse(vs3.intersectsRay(0.5f, 0, 0.5f, 0.5f, 1, 0.5f));

var vs4 = VoxelShape
.builder()
.solid(0, 0, 0, 1, 1, 1)
.vacancy(0, 0, 0, 1, 1, 1)
.build();
assertFalse(vs4.intersectsRay(0, 0, 0, 1, 1, 1));

var vs5 = VoxelShape
.builder()
.solid(0, 0, 0, 1, 1, 1)
.vacancy(0, 0, 0, 1, 0.4f, 1)
.vacancy(0, 0.6f, 0, 1, 1, 1)
.build();
assertTrue(vs5.intersectsRay(0, 0, 0, 1, 1, 1));

var vs6 = VoxelShape
.builder()
.vacancy(0, 0.4f, 0, 1, 0.6f, 1)
.solid(0, 0, 0, 1, 0.4f, 1)
.solid(0, 0.6f, 0, 1, 1, 1)
.build();
assertTrue(vs6.intersectsRay(0, 0, 0, 1, 1, 1));
assertFalse(vs6.intersectsRay(0, 0.4f, 0, 1, 0.4f, 1));
assertFalse(vs6.intersectsRay(0, 0.41f, 0, 1, 0.41f, 1));
assertFalse(vs6.intersectsRay(0, 0.59f, 0, 1, 0.59f, 1));
assertFalse(vs6.intersectsRay(0, 0.6f, 0, 1, 0.6f, 1));

var vs7 = VoxelShape
.builder()
.solid(0, 0, 0, 1, 0.9f, 1)
.solid(0, 0.1f, 0, 1, 1, 1)
.vacancy(0.4f, 0, 0.4f, 0.6f, 1, 0.6f)
.build();
assertFalse(vs7.intersectsRay(0.5f, 0, 0.5f, 0.5f, 1, 0.5f));
}
}

0 comments on commit dc360b6

Please sign in to comment.