Solved Particle ray with block hitbox

Discussion in 'Spigot Plugin Development' started by LordMorgoth, Jul 8, 2018.

  1. Hello,

    I'm trying to create a kind of Quake plugin with a ray made of particles.
    I've found this thread : https://www.spigotmc.org/threads/hitboxes-and-ray-tracing.174358/ and this code to detect if a player is looking to another player :
    Code (Java):
    // Returns first player in the specified player's line of sight
    // up to max blocks away, or null if none.
    public static Player getTargetPlayer(Player player, int max) {
        List<Player> possible = player.getNearbyEntities(max, max, max).stream().filter(entity -> entity instanceof Player).map(entity -> (Player) entity).filter(player::hasLineOfSight).collect(Collectors.toList());
        Ray ray = Ray.from(player);
        double d = -1;
        Player closest = null;
        for (Player player1 : possible) {
            double dis = AABB.from(player1).collidesD(ray, 0, max);
            if (dis != -1) {
                if (dis < d || d == -1) {
                    d = dis;
                    closest = player1;
                }
            }
        }
        return closest;
    }
    With those classes :
    Ray - http://pastebin.com/eHQawAme
    AABB - http://pastebin.com/PV63ZkQy
    This code works perfectly, but I'd like to add an effect of particle on the player looking direction while the ray don't pass through a bloc (includes hitbox of blocs like stairs, slabs or fences).
    How can I do this ?

    Thanks in advance,
    LordMorgoth

    (PS : I'm French, sorry for the possible mistakes :oops:)
     
  2. There's a link to a tutorial on how to draw a particle line in my signature, maybe that helps.
     
  3. Let me grab that quickly, hold on.
    This will spawn a line of particles (increase the tick to make the line go slower) and will stop when it hits a solid block.
    Code (Text):
    new BukkitRunnable() {
    Location start = player.getEyeLocation();
    Vector increase = start.getDirection();
    int counter;
    public void run() {
    if (counter == 40) {
    cancel();
    } else {
    Location point = start.add(increase);
    if (point.getBlock().getType().isSolid()) {
    cancel();
    }
    // Play effect at point.
    counter++;
    }
    }
    }.runTaskTimer(Maincore.instance, 0, 0);
     
  4. Thank you for your replies.
    I suppose you want to talk about this tutorial. In this one, you're only explaining how to draw the ray.
    Here it's the same as in the @finnbon 's answer.

    My issue would be rather : how can I determine if a location is in the filled part of a block ? With, as I said before, according to the empty space in the blocks like stairs, slabs or fences.

    Thanks
     
  5. blocktype#isSolid();
     
  6. This method doesn't return the correct value for a non-cubic block. For instance, a bloc like a slab is solid, but the half of the bloc is empty.
     
  7. Material::isOccluding, may be what you're looking for.
     
  8. No no no! Come on, people.. First step to helping someone is understanding their issue..

    There no method in the API that does what you want.

    However, I have put together information from various designs and have a solution for you, here. That is my method for finding exactly what block a player is looking at, with proper collision box detection. It's able to look passed the blank areas of stairs, slabs, signs, and any other non-full blocks.

    Here's some video demonstrations:





    Feel free to modify the code and use the raytrace to draw your particles until it reaches an intersecting block's collision box. ;)
     
    #8 BillyGalbreath, Jul 9, 2018
    Last edited: Jul 9, 2018
    • Like Like x 1
    • Winner Winner x 1
    • Useful Useful x 1
  9. Thanks @BillyGalbreath
    Your method works perfectly, except that it return a bloc. I'd like to have the precise coordinates decimal of the intersect of the block (for instance, the ray can intersect the block in its middle, but the method return a block, with which I get its coordinates, but it's not the coordinates of the intersection, it's those of the block).
     
  10. Thats why I said you would have to modify it a bit. The loop is there, just store the coordinates in each loop until you reach a block. dont care about the block though, just those coords you stored for the line/ray of particles.
     
  11. I searched, but in which loop can I get the coordinates of the intersection ? In LineOfSeight.java or RayTrace.java ?
    Thanks again
     
  12. LineOfSight.

    Do you understand what the code is doing at all? It's really quite simple.

    First, it creates a raytrace object using the player's eye location and direction.

    Second it traverses this raytrace object with a specified accuracy and maximum length. The more accurate you want the slower/laggier this will be. It stores all the positions it traversed.

    The next section of code is something you don't need at all. It's the first loop that basically makes sure I only have 1 position per block location. This ensures I'm not testing a block multiple times in the next section.

    The last section is where I loop over the positions and test each blocks colliding box collides with the raytrace. If it does, then that what block the player is looking at. If not, I test the next position. Etc.

    That's all the accuracy my use-case needed.

    Now, what you want to do is pretty much remove the step where I only use block positions. Or maybe even mix the two for efficiency.
     
  13. Thanks a lot !
    I modified the method getTargetBlock like this, and all works perfectly ;) :
    Code (Java):

        public static Location getTargetLocation(Player player) {
            World world = player.getWorld();
            RayTrace rayTrace = new RayTrace(player.getEyeLocation());
            ArrayList<Vector> positions = rayTrace.traverse(250, 0.1);

            for (Vector pos : positions) {
                Block block = pos.toBlockVector().toLocation(world).getBlock();
                if (block.getType() == Material.AIR || block.getType() == Material.WATER || block.getType() == Material.STATIONARY_WATER) {
                    continue; // ignore air and water blocks
                }
                BoundingBox box = new BoundingBox(block);
                for (Vector position : positions) {
                    if (rayTrace.intersects(position, box.getMin(), box.getMax()))
                        return new Location(world, pos.getX(), pos.getY(), pos.getZ()); // return first non-air location in line of sight
                }
            }

            return null;
        }
     
    • Like Like x 1
  14. Instead of traversing through the ray, can't you just iterate through the blocks on the line and check for ray intersection? It's probably more efficient.
    https://gist.github.com/aadnk/7123926

    EDIT: If it's just whether the ray hits or not, then traversing should be quick. If you care about the intersection point, then you'll need to do the ray-hitbox intersection algorithm.
     
  15. That's why I suggested mixing the two approaches. First, iterate block locations for a hit. Then start checking right before that hit at higher accuracy.
    That way at 0.1 accuracy you're not doing 100 checks across 10 blocks of air. You'd just do 10. Then start at 9.5 and work in 0.1 intervals until you get the accurate hit.

    250 blocks out at 0.1 accuracy is 2500 checks. Can be quite laggy if a player is looking out that far. Mixing the two approaches can reduce that to ~250-270 checks.
     
  16. That's fine, I will take into account all this for a better efficiency :)