tutorial:pixel_raycast
Differences
This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
| tutorial:pixel_raycast [2020/08/14 16:46] – created emmanuelmess | tutorial:pixel_raycast [2023/11/30 22:33] (current) – [For arbitrary reach] Naming fix famrofexl | ||
|---|---|---|---|
| Line 5: | Line 5: | ||
| All of this is client side. | All of this is client side. | ||
| - | There are two types, center pixel (crosshair) and arbitrary pixel. | + | There are two cases, center pixel (crosshair) and arbitrary pixel. |
| ===== Special case: Center pixel ===== | ===== Special case: Center pixel ===== | ||
| Line 32: | Line 32: | ||
| </ | </ | ||
| + | ==== 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 <code java> HitResult hit = client.crosshairTarget;</ | ||
| + | |||
| + | <code java [enable_line_numbers=" | ||
| + | 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, | ||
| + | </ | ||
| + | |||
| + | ===== General case: Arbitrary pixel ===== | ||
| + | |||
| + | Example [[https:// | ||
| + | |||
| + | For this one we need to precalculate a few things: | ||
| + | |||
| + | <code java [enable_line_numbers=" | ||
| + | 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;// | ||
| + | } | ||
| + | |||
| + | 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: | ||
| + | <code java [enable_line_numbers=" | ||
| + | private static Vec3d map(float anglePerPixel, | ||
| + | Vector3f verticalRotationAxis, | ||
| + | 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# | ||
| + | <code java [enable_line_numbers=" | ||
| + | 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();// | ||
| + | HitResult target = raycast(entity, | ||
| + | boolean tooFar = false; | ||
| + | double extendedReach = reachDistance; | ||
| + | if (client.interactionManager.hasExtendedReach()) { | ||
| + | extendedReach = 6.0D;// | ||
| + | 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, | ||
| + | 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: | ||
| + | <code java [enable_line_numbers=" | ||
| + | Vec3d direction = map( | ||
| + | (float) angleSize, | ||
| + | cameraDirection, | ||
| + | horizontalRotationAxis, | ||
| + | verticalRotationAxis, | ||
| + | x, | ||
| + | y, | ||
| + | width, | ||
| + | height | ||
| + | ); | ||
| + | HitResult hit = raycastInDirection(client, | ||
| + | |||
| + | 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:// | ||
tutorial/pixel_raycast.1597423572.txt.gz · Last modified: 2020/08/14 16:46 by emmanuelmess