====== Pixel raycast ======
Suppose you want to know what block or entity corresponds to a pixel on the screen. This can be done with pixel raycast.
All of this is client side.
There are two cases, center pixel (crosshair) and arbitrary pixel.
===== Special case: Center pixel =====
This can be done with:
MinecraftClient client = MinecraftClient.getInstance();
HitResult hit = client.crosshairTarget;
switch(hit.getType()) {
case Type.MISS:
//nothing near enough
break;
case Type.BLOCK:
BlockHitResult blockHit = (BlockHitResult) hit;
BlockPos blockPos = blockHit.getBlockPos();
BlockState blockState = client.world.getBlockState(blockPos);
Block block = blockState.getBlock();
break;
case Type.ENTITY:
EntityHitResult entityHit = (EntityHitResult) hit;
Entity entity = entityHit.getEntity();
break;
}
==== For arbitrary reach ====
The code above allows for the normal reach, 3 blocks for survival and 4.5 in creative. If you want the raycast to reach farther you need to replace HitResult hit = client.crosshairTarget;
in [[pixel_raycast#special_casecenter_pixel | Special Case: Center Pixel]] with the example below:
double maxReach = 1000; //The farthest target the cameraEntity can detect
float tickDelta = 1.0F; //Used for tracking animation progress; no tracking is 1.0F
boolean includeFluids = true; //Whether to detect fluids as blocks
HitResult hit = client.cameraEntity.raycast(maxReach, tickDelta, includeFluids);
===== General case: Arbitrary pixel =====
Example [[https://github.com/EmmanuelMess/BoundingBoxMinecraftMod|here]].
For this one we need to precalculate a few things:
MinecraftClient client = MinecraftClient.getInstance();
int width = client.getWindow().getScaledWidth();
int height = client.getWindow().getScaledHeight();
Vec3d cameraDirection = client.cameraEntity.getRotationVec(tickDelta);
double fov = client.options.fov;
double angleSize = fov/height;
Vector3f verticalRotationAxis = new Vector3f(cameraDirection);
verticalRotationAxis.cross(Vector3f.POSITIVE_Y);
if(!verticalRotationAxis.normalize()) {
return;//The camera is pointing directly up or down, you'll have to fix this one
}
Vector3f horizontalRotationAxis = new Vector3f(cameraDirection);
horizontalRotationAxis.cross(verticalRotationAxis);
horizontalRotationAxis.normalize();
verticalRotationAxis = new Vector3f(cameraDirection);
verticalRotationAxis.cross(horizontalRotationAxis);
Once you have that, you can use this function to calculate the direction vector that corresponds to the pixel:
private static Vec3d map(float anglePerPixel, Vec3d center, Vector3f horizontalRotationAxis,
Vector3f verticalRotationAxis, int x, int y, int width, int height) {
float horizontalRotation = (x - width/2f) * anglePerPixel;
float verticalRotation = (y - height/2f) * anglePerPixel;
final Vector3f temp2 = new Vector3f(center);
temp2.rotate(verticalRotationAxis.getDegreesQuaternion(verticalRotation));
temp2.rotate(horizontalRotationAxis.getDegreesQuaternion(horizontalRotation));
return new Vec3d(temp2);
}
Then you have this reimplementation of the code at GameRenderer#updateTargetedEntity:
private static HitResult raycastInDirection(MinecraftClient client, float tickDelta, Vec3d direction) {
Entity entity = client.getCameraEntity();
if (entity == null || client.world == null) {
return null;
}
double reachDistance = client.interactionManager.getReachDistance();//Change this to extend the reach
HitResult target = raycast(entity, reachDistance, tickDelta, false, direction);
boolean tooFar = false;
double extendedReach = reachDistance;
if (client.interactionManager.hasExtendedReach()) {
extendedReach = 6.0D;//Change this to extend the reach
reachDistance = extendedReach;
} else {
if (reachDistance > 3.0D) {
tooFar = true;
}
}
Vec3d cameraPos = entity.getCameraPosVec(tickDelta);
extendedReach = extendedReach * extendedReach;
if (target != null) {
extendedReach = target.getPos().squaredDistanceTo(cameraPos);
}
Vec3d vec3d3 = cameraPos.add(direction.multiply(reachDistance));
Box box = entity
.getBoundingBox()
.stretch(entity.getRotationVec(1.0F).multiply(reachDistance))
.expand(1.0D, 1.0D, 1.0D);
EntityHitResult entityHitResult = ProjectileUtil.raycast(
entity,
cameraPos,
vec3d3,
box,
(entityx) -> !entityx.isSpectator() && entityx.collides(),
extendedReach
);
if (entityHitResult == null) {
return target;
}
Entity entity2 = entityHitResult.getEntity();
Vec3d vec3d4 = entityHitResult.getPos();
double g = cameraPos.squaredDistanceTo(vec3d4);
if (tooFar && g > 9.0D) {
return null;
} else if (g < extendedReach || target == null) {
target = entityHitResult;
if (entity2 instanceof LivingEntity || entity2 instanceof ItemFrameEntity) {
client.targetedEntity = entity2;
}
}
return target;
}
private static HitResult raycast(
Entity entity,
double maxDistance,
float tickDelta,
boolean includeFluids,
Vec3d direction
) {
Vec3d end = entity.getCameraPosVec(tickDelta).add(direction.multiply(maxDistance));
return entity.world.raycast(new RaycastContext(
entity.getCameraPosVec(tickDelta),
end,
RaycastContext.ShapeType.OUTLINE,
includeFluids ? RaycastContext.FluidHandling.ANY : RaycastContext.FluidHandling.NONE,
entity
));
}
Once you have the direction and the raycaster, you can put them together:
Vec3d direction = map(
(float) angleSize,
cameraDirection,
horizontalRotationAxis,
verticalRotationAxis,
x,
y,
width,
height
);
HitResult hit = raycastInDirection(client, tickDelta, direction);
switch(hit.getType()) {
case Type.MISS:
//nothing near enough
break;
case Type.BLOCK:
BlockHitResult blockHit = (BlockHitResult) hit;
BlockPos blockPos = blockHit.getBlockPos();
BlockState blockState = client.world.getBlockState(blockPos);
Block block = blockState.getBlock();
break;
case Type.ENTITY:
EntityHitResult entityHit = (EntityHitResult) hit;
Entity entity = entityHit.getEntity();
break;
}
Here x and y are your pixel coordinates.
==== Performance considerations ====
This is EXPENSIVE, if you do it too many times, it WILL get slow. Especially for long reaches. If you **need** to do many raycasts in a single frame, [[https://stackoverflow.com/q/777997/3124150|this]] link might be helpful.