User Tools

Site Tools


Sidebar

Setup

Basics

Items

Blocks and Block Entities

Fluids

Entities

World Generation

Miscellaneous

Events

Mixins

Advanced

Tutorials for Minecraft 1.15

Tutorials for Minecraft 1.14

Documentation

tutorial:pixel_raycast

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:

  1. MinecraftClient client = MinecraftClient.getInstance();
  2. HitResult hit = client.crosshairTarget;
  3.  
  4. switch(hit.getType()) {
  5. case Type.MISS:
  6. //nothing near enough
  7. break;
  8. case Type.BLOCK:
  9. BlockHitResult blockHit = (BlockHitResult) hit;
  10. BlockPos blockPos = blockHit.getBlockPos();
  11. BlockState blockState = client.world.getBlockState(blockPos);
  12. Block block = blockState.getBlock();
  13. break;
  14. case Type.ENTITY:
  15. EntityHitResult entityHit = (EntityHitResult) hit;
  16. Entity entity = entityHit.getEntity();
  17. break;
  18. }

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 use the general case below.

General case: Arbitrary pixel

Example here.

For this one we need to precalculate a few things:

  1. MinecraftClient client = MinecraftClient.getInstance();
  2. int width = client.getWindow().getScaledWidth();
  3. int height = client.getWindow().getScaledHeight();
  4. Vec3d cameraDirection = client.cameraEntity.getRotationVec(tickDelta);
  5. double fov = client.options.fov;
  6. double angleSize = fov/height;
  7. Vector3f verticalRotationAxis = new Vector3f(cameraDirection);
  8. verticalRotationAxis.cross(Vector3f.POSITIVE_Y);
  9. if(!verticalRotationAxis.normalize()) {
  10. return;//The camera is pointing directly up or down, you'll have to fix this one
  11. }
  12.  
  13. Vector3f horizontalRotationAxis = new Vector3f(cameraDirection);
  14. horizontalRotationAxis.cross(verticalRotationAxis);
  15. horizontalRotationAxis.normalize();
  16.  
  17. verticalRotationAxis = new Vector3f(cameraDirection);
  18. verticalRotationAxis.cross(horizontalRotationAxis);

Once you have that, you can use this function to calculate the direction vector that corresponds to the pixel:

  1. private static Vec3d map(float anglePerPixel, Vec3d center, Vector3f horizontalRotationAxis,
  2. Vector3f verticalRotationAxis, int x, int y, int width, int height) {
  3. float horizontalRotation = (x - width/2f) * anglePerPixel;
  4. float verticalRotation = (y - height/2f) * anglePerPixel;
  5.  
  6. final Vector3f temp2 = new Vector3f(center);
  7. temp2.rotate(verticalRotationAxis.getDegreesQuaternion(verticalRotation));
  8. temp2.rotate(horizontalRotationAxis.getDegreesQuaternion(horizontalRotation));
  9. return new Vec3d(temp2);
  10. }

Then you have this reimplementation of the code at GameRenderer#updateTargetedEntity:

  1. private static HitResult raycastInDirection(MinecraftClient client, float tickDelta, Vec3d direction) {
  2. Entity entity = client.getCameraEntity();
  3. if (entity == null || client.world == null) {
  4. return null;
  5. }
  6.  
  7. double reachDistance = client.interactionManager.getReachDistance();//Change this to extend the reach
  8. HitResult target = raycast(entity, reachDistance, tickDelta, false, direction);
  9. boolean tooFar = false;
  10. double extendedReach = reachDistance;
  11. if (client.interactionManager.hasExtendedReach()) {
  12. extendedReach = 6.0D;//Change this to extend the reach
  13. reachDistance = extendedReach;
  14. } else {
  15. if (reachDistance > 3.0D) {
  16. tooFar = true;
  17. }
  18. }
  19.  
  20. Vec3d cameraPos = entity.getCameraPosVec(tickDelta);
  21.  
  22. extendedReach = extendedReach * extendedReach;
  23. if (target != null) {
  24. extendedReach = target.getPos().squaredDistanceTo(cameraPos);
  25. }
  26.  
  27. Vec3d vec3d3 = cameraPos.add(direction.multiply(reachDistance));
  28. Box box = entity
  29. .getBoundingBox()
  30. .stretch(entity.getRotationVec(1.0F).multiply(reachDistance))
  31. .expand(1.0D, 1.0D, 1.0D);
  32. EntityHitResult entityHitResult = ProjectileUtil.raycast(
  33. entity,
  34. cameraPos,
  35. vec3d3,
  36. box,
  37. (entityx) -> !entityx.isSpectator() && entityx.collides(),
  38. extendedReach
  39. );
  40.  
  41. if (entityHitResult == null) {
  42. return target;
  43. }
  44.  
  45. Entity entity2 = entityHitResult.getEntity();
  46. Vec3d vec3d4 = entityHitResult.getPos();
  47. double g = cameraPos.squaredDistanceTo(vec3d4);
  48. if (tooFar && g > 9.0D) {
  49. return null;
  50. } else if (g < extendedReach || target == null) {
  51. target = entityHitResult;
  52. if (entity2 instanceof LivingEntity || entity2 instanceof ItemFrameEntity) {
  53. client.targetedEntity = entity2;
  54. }
  55. }
  56.  
  57. return target;
  58. }
  59.  
  60. private static HitResult raycast(
  61. Entity entity,
  62. double maxDistance,
  63. float tickDelta,
  64. boolean includeFluids,
  65. Vec3d direction
  66. ) {
  67. Vec3d end = entity.getCameraPosVec(tickDelta).add(direction.multiply(maxDistance));
  68. return entity.world.raycast(new RaycastContext(
  69. entity.getCameraPosVec(tickDelta),
  70. end,
  71. RaycastContext.ShapeType.OUTLINE,
  72. includeFluids ? RaycastContext.FluidHandling.ANY : RaycastContext.FluidHandling.NONE,
  73. entity
  74. ));
  75. }

Once you have the direction and the raycaster, you can put them together:

  1. Vec3d direction = map(
  2. (float) angleSize,
  3. cameraDirection,
  4. horizontalRotationAxis,
  5. verticalRotationAxis,
  6. x,
  7. y,
  8. width,
  9. height
  10. );
  11. HitResult hit = raycastInDirection(client, tickDelta, direction);
  12.  
  13. switch(hit.getType()) {
  14. case Type.MISS:
  15. //nothing near enough
  16. break;
  17. case Type.BLOCK:
  18. BlockHitResult blockHit = (BlockHitResult) hit;
  19. BlockPos blockPos = blockHit.getBlockPos();
  20. BlockState blockState = client.world.getBlockState(blockPos);
  21. Block block = blockState.getBlock();
  22. break;
  23. case Type.ENTITY:
  24. EntityHitResult entityHit = (EntityHitResult) hit;
  25. Entity entity = entityHit.getEntity();
  26. break;
  27. }

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, this link might be helpful.

tutorial/pixel_raycast.txt · Last modified: 2020/08/26 08:22 by snakefangox