Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional ray trace API #12162

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.papermc.paper.raytrace;

/**
* Determines the collision behavior when blocks get hit during ray tracing.
*/
public enum BlockCollisionMode {

/**
* Use the collision shape.
*/
COLLIDER,
/**
* Use the outline shape.
*/
OUTLINE,
/**
* Use the visual shape.
*/
VISUAL,
/**
* Use the shape of a full block, but only consider blocks tagged with {@link org.bukkit.Tag#FALL_DAMAGE_RESETTING}.
*/
FALL_DAMAGE_RESETTING
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels kinda weird to expose this one, as i think it would feel pretty random to someone using the API. And its only really there for internal Falldamage calculations

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, you should be able to replicate it yourself with the predicate anyways right?

Copy link
Contributor Author

@TonytheMacaroni TonytheMacaroni Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda - for some reason, this mode always uses a full block shape, instead of the actual collision shape of the block. I do agree that I don't see too much of a point of keeping it, beyond perhaps making replicating fall damage calculations in API easier to do. I don't see much of a point to not include it, though.

Copy link
Contributor

@notTamion notTamion Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes the enums we have in the API seem rather random and odly specific, same thing for the WATER, LAVA Review i left. If this uses a collision mode (full block shape) we don‘t otherwise have in the api then we should instead expose that one without the fall damage filtering part, that seems a lot more useful.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally am also on the side to expose a much as we need, as little as we can.

Overexposing internals for the sake of exposing them just makes your life harder when things change.


}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.papermc.paper.raytracing;

import java.util.function.Predicate;
import io.papermc.paper.raytrace.BlockCollisionMode;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.block.Block;
Expand Down Expand Up @@ -46,26 +47,25 @@ public interface PositionedRayTraceConfigurationBuilder {

/**
* Sets the FluidCollisionMode when looking for block collisions.
* <p>
* If collisions with passable blocks are ignored, fluid collisions are
* ignored as well regardless of the fluid collision mode.
*
* @param fluidCollisionMode the new FluidCollisionMode
* @return a reference to this object
*/
@Contract(value = "_ -> this", mutates = "this")
PositionedRayTraceConfigurationBuilder fluidCollisionMode(FluidCollisionMode fluidCollisionMode);

/**
* Sets the BlockCollisionMode when looking for block collisions.
*
* @param blockCollisionMode the new BlockCollisionMode
* @return a reference to this object
*/
@Contract(value = "_ -> this", mutates = "this")
PositionedRayTraceConfigurationBuilder blockCollisionMode(BlockCollisionMode blockCollisionMode);

/**
* Sets whether the raytrace should ignore passable blocks when looking for
* block collisions.
* <p>
* If collisions with passable blocks are ignored, fluid collisions are
* ignored as well regardless of the fluid collision mode.
* <p>
* Portal blocks are only considered passable if the ray starts within them.
* Apart from that collisions with portal blocks will be considered even if
* collisions with passable blocks are otherwise ignored.
*
* @param ignorePassableBlocks if the raytrace should ignore passable blocks
* @return a reference to this object
Expand Down
4 changes: 4 additions & 0 deletions paper-api/src/main/java/org/bukkit/FluidCollisionMode.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ public enum FluidCollisionMode {
* Only collide with source fluid blocks.
*/
SOURCE_ONLY,
/**
* Collide only with water.
*/
WATER,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are going to expose water we should add lava to the nms enum and add it here aswell

/**
* Collide with all fluids.
*/
Expand Down
32 changes: 0 additions & 32 deletions paper-api/src/main/java/org/bukkit/World.java
Original file line number Diff line number Diff line change
Expand Up @@ -1844,13 +1844,6 @@ default Iterable<? extends net.kyori.adventure.audience.Audience> audiences() {
* Performs a ray trace that checks for block collisions using the blocks'
* precise collision shapes.
* <p>
* If collisions with passable blocks are ignored, fluid collisions are
* ignored as well regardless of the fluid collision mode.
* <p>
* Portal blocks are only considered passable if the ray starts within
* them. Apart from that collisions with portal blocks will be considered
* even if collisions with passable blocks are otherwise ignored.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
Expand All @@ -1865,18 +1858,10 @@ default Iterable<? extends net.kyori.adventure.audience.Audience> audiences() {
@Nullable
public RayTraceResult rayTraceBlocks(@NotNull Location start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks);

// Paper start
/**
* Performs a ray trace that checks for block collisions using the blocks'
* precise collision shapes.
* <p>
* If collisions with passable blocks are ignored, fluid collisions are
* ignored as well regardless of the fluid collision mode.
* <p>
* Portal blocks are only considered passable if the ray starts within
* them. Apart from that collisions with portal blocks will be considered
* even if collisions with passable blocks are otherwise ignored.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
Expand All @@ -1891,7 +1876,6 @@ default Iterable<? extends net.kyori.adventure.audience.Audience> audiences() {
* @return the ray trace hit result, or <code>null</code> if there is no hit
*/
@Nullable RayTraceResult rayTraceBlocks(io.papermc.paper.math.@NotNull Position start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, @Nullable Predicate<? super Block> canCollide);
// Paper end

/**
* Performs a ray trace that checks for both block and entity collisions.
Expand All @@ -1900,13 +1884,6 @@ default Iterable<? extends net.kyori.adventure.audience.Audience> audiences() {
* <code>raySize</code> parameter is only taken into account for entity
* collision checks.
* <p>
* If collisions with passable blocks are ignored, fluid collisions are
* ignored as well regardless of the fluid collision mode.
* <p>
* Portal blocks are only considered passable if the ray starts within them.
* Apart from that collisions with portal blocks will be considered even if
* collisions with passable blocks are otherwise ignored.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
Expand All @@ -1926,21 +1903,13 @@ default Iterable<? extends net.kyori.adventure.audience.Audience> audiences() {
@Nullable
public RayTraceResult rayTrace(@NotNull Location start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, @Nullable Predicate<? super Entity> filter);

// Paper start
/**
* Performs a ray trace that checks for both block and entity collisions.
* <p>
* Block collisions use the blocks' precise collision shapes. The
* <code>raySize</code> parameter is only taken into account for entity
* collision checks.
* <p>
* If collisions with passable blocks are ignored, fluid collisions are
* ignored as well regardless of the fluid collision mode.
* <p>
* Portal blocks are only considered passable if the ray starts within them.
* Apart from that collisions with portal blocks will be considered even if
* collisions with passable blocks are otherwise ignored.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
Expand All @@ -1960,7 +1929,6 @@ default Iterable<? extends net.kyori.adventure.audience.Audience> audiences() {
* entity, or <code>null</code> if there is no hit
*/
@Nullable RayTraceResult rayTrace(io.papermc.paper.math.@NotNull Position start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, @Nullable Predicate<? super Entity> filter, @Nullable Predicate<? super Block> canCollide);
// Paper end

/**
* Performs a ray trace that checks for collisions with the specified
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.EnumSet;
import java.util.OptionalDouble;
import java.util.function.Predicate;
import io.papermc.paper.raytrace.BlockCollisionMode;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.block.Block;
Expand All @@ -19,7 +20,7 @@ public class PositionedRayTraceConfigurationBuilderImpl implements PositionedRay
public @Nullable Vector direction;
public OptionalDouble maxDistance = OptionalDouble.empty();
public FluidCollisionMode fluidCollisionMode = FluidCollisionMode.NEVER;
public boolean ignorePassableBlocks;
public BlockCollisionMode blockCollisionMode = BlockCollisionMode.OUTLINE;
public double raySize = 0.0D;
public @Nullable Predicate<? super Entity> entityFilter;
public @Nullable Predicate<? super Block> blockFilter;
Expand Down Expand Up @@ -53,9 +54,16 @@ public PositionedRayTraceConfigurationBuilder fluidCollisionMode(final FluidColl
return this;
}

@Override
public PositionedRayTraceConfigurationBuilder blockCollisionMode(final BlockCollisionMode blockCollisionMode) {
Preconditions.checkArgument(blockCollisionMode != null, "blockCollisionMode must not be null");
this.blockCollisionMode = blockCollisionMode;
return this;
}

@Override
public PositionedRayTraceConfigurationBuilder ignorePassableBlocks(final boolean ignorePassableBlocks) {
this.ignorePassableBlocks = ignorePassableBlocks;
this.blockCollisionMode = ignorePassableBlocks ? BlockCollisionMode.COLLIDER : BlockCollisionMode.OUTLINE;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public static Fluid toNMS(FluidCollisionMode fluidCollisionMode) {
switch (fluidCollisionMode) {
case ALWAYS:
return Fluid.ANY;
case WATER:
return Fluid.WATER;
case SOURCE_ONLY:
return Fluid.SOURCE_ONLY;
case NEVER:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,10 @@ public RayTraceResult rayTraceBlocks(Location start, Vector direction, double ma

@Override
public RayTraceResult rayTraceBlocks(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, Predicate<? super Block> canCollide) {
return this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks ? io.papermc.paper.raytrace.BlockCollisionMode.COLLIDER : io.papermc.paper.raytrace.BlockCollisionMode.OUTLINE, canCollide);
}

public RayTraceResult rayTraceBlocks(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, io.papermc.paper.raytrace.BlockCollisionMode blockCollisionMode, Predicate<? super Block> canCollide) {
Preconditions.checkArgument(start != null, "Location start cannot be null");
Preconditions.checkArgument(!(start instanceof Location location) || this.equals(location.getWorld()), "Location start cannot be in a different world");
Preconditions.checkArgument(start.isFinite(), "Location start is not finite");
Expand All @@ -1169,6 +1173,7 @@ public RayTraceResult rayTraceBlocks(io.papermc.paper.math.Position start, Vecto

Preconditions.checkArgument(direction.lengthSquared() > 0, "Direction's magnitude (%s) need to be greater than 0", direction.lengthSquared());
Preconditions.checkArgument(fluidCollisionMode != null, "FluidCollisionMode cannot be null");
Preconditions.checkArgument(blockCollisionMode != null, "BlockCollisionMode cannot be null");

if (maxDistance < 0.0D) {
return null;
Expand All @@ -1177,7 +1182,7 @@ public RayTraceResult rayTraceBlocks(io.papermc.paper.math.Position start, Vecto
Vector dir = direction.clone().normalize().multiply(maxDistance);
Vec3 startPos = io.papermc.paper.util.MCUtil.toVec3(start); // Paper - Add predicate for blocks when raytracing
Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ());
HitResult nmsHitResult = this.getHandle().clip(new ClipContext(startPos, endPos, ignorePassableBlocks ? ClipContext.Block.COLLIDER : ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty()), canCollide); // Paper - Add predicate for blocks when raytracing
HitResult nmsHitResult = this.getHandle().clip(new ClipContext(startPos, endPos, ClipContext.Block.values()[blockCollisionMode.ordinal()], CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty()), canCollide);

return CraftRayTraceResult.fromNMS(this, nmsHitResult);
}
Expand All @@ -1190,8 +1195,11 @@ public RayTraceResult rayTrace(Location start, Vector direction, double maxDista

@Override
public RayTraceResult rayTrace(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate<? super Entity> filter, Predicate<? super Block> canCollide) {
RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, canCollide);
// Paper end - Add predicate for blocks when raytracing
return this.rayTrace(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks ? io.papermc.paper.raytrace.BlockCollisionMode.COLLIDER : io.papermc.paper.raytrace.BlockCollisionMode.OUTLINE, raySize, filter, null);
}

public RayTraceResult rayTrace(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, io.papermc.paper.raytrace.BlockCollisionMode blockCollisionMode, double raySize, Predicate<? super Entity> filter, Predicate<? super Block> canCollide) {
RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, blockCollisionMode, canCollide);
Vector startVec = null;
double blockHitDistance = maxDistance;

Expand Down Expand Up @@ -1232,11 +1240,11 @@ public RayTraceResult rayTrace(Consumer<PositionedRayTraceConfigurationBuilder>
final double maxDistance = builder.maxDistance.getAsDouble();
if (builder.targets.contains(RayTraceTarget.ENTITY)) {
if (builder.targets.contains(RayTraceTarget.BLOCK)) {
return this.rayTrace(builder.start, builder.direction, maxDistance, builder.fluidCollisionMode, builder.ignorePassableBlocks, builder.raySize, builder.entityFilter, builder.blockFilter);
return this.rayTrace(builder.start, builder.direction, maxDistance, builder.fluidCollisionMode, builder.blockCollisionMode, builder.raySize, builder.entityFilter, builder.blockFilter);
}
return this.rayTraceEntities(builder.start, builder.direction, maxDistance, builder.raySize, builder.entityFilter);
}
return this.rayTraceBlocks(builder.start, builder.direction, maxDistance, builder.fluidCollisionMode, builder.ignorePassableBlocks, builder.blockFilter);
return this.rayTraceBlocks(builder.start, builder.direction, maxDistance, builder.fluidCollisionMode, builder.blockCollisionMode, builder.blockFilter);
}

@Override
Expand Down
Loading