Solved Get the exact point on the face of the selected block

Discussion in 'Plugin Development' started by Maurdekye, Apr 16, 2014.

Thread Status:
Not open for further replies.
  1. Offline

    Maurdekye

    I know how to get the face of the block that a player is looking at using BlockIterators, but what I want is something more specific; the precise point on that face that the player is viewing. I'm aware that some math is involved (something to do with ray-tracing from what I can tell), but i've no idea where to go from there. If anyone could help provide examples, or explain it in more detail, that'd be great.

    Didn't think this would be that hard; apparently I was wrong.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 7, 2016
  2. Offline

    Maurdekye

    Can anybody help me with this?
     
  3. Offline

    !Phoenix!

    That reminds me of something. Let me check...
    There it is: SignCodePad
    Obviously this plugin has to be able to do something like that. Looks like the project is available on GitHub, so you can check out the code. But do not forget to post your results if you find something! :)
     
  4. Offline

    Maurdekye

    @!Phoenix! Huh.... interesting. I'll see if there's anything in there I could use, but I was kind of hoping for a more direct answer. Thanks anyway, though.
     
  5. Offline

    !Phoenix!

    Welcome. At least it's a start. Maybe the developer can help you out as well.

    Small hint on the side: Notice that you should only bump a topic every 24h
     
  6. Offline

    Maurdekye

    Well, I'm still looking for a more direct answer; I can't exactly be bothered to scrub through all of his code.
     
  7. Offline

    oscarshi1995

    there is no "Simple" way to do this as bukkit does not have a built in "getClickPos" you would have to do some hacky stuff with player head angles and distance to block etc.
     
  8. Offline

    Maurdekye

    oscarshi1995 I know that, and that's precisely what i'm looking for. If you can help, then please do. But otherwise, just don't post.
     
  9. Offline

    oscarshi1995

  10. Offline

    desht

    Maurdekye I actually had a play with this not long ago, and came up with this (yes, you need to calculate the intersection of a line and plane, as oscarshi1995 pointed out):

    https://github.com/desht/dhutils/bl...va/me/desht/dhutils/block/BlockUtil.java#L106

    You're free to use it if you like; you'll need the getTargetPoint() method as well as the private isectLinePlane() method and the BlockAndPosition class.

    It works like the current Bukkit getTargetBlock() method, but instead returns the block, the face the block was intersected at, and the exact point at which the player's line of sight intersected the block.

    There is a problem, though, which is that the precise bounding box for a given material type is not available via Bukkit. The information is in Craftbukkit (see the minX/maxX/minY/maxY/minZ/maxZ fields in the NMS Block class), and it wouldn't be overly difficult to make it available, but I didn't get that far. You'll notice that I added one cheaty case just for signs, but the method should really check for the bounding box of any block. This would also fix the very annoying problem with the current getTargetBlock() method where a flat block like a carpet or pressure plate will be returned if it's in front of the wall that a player is actually looking at.
     
    NathanWolf likes this.
  11. Offline

    Maurdekye

    desht Ah, thank you very much! This is incredibly helpful; thank you for lending me your code.

    desht The method isectLinePlane(), if you don't mind; could you explain it in more detail? I've been trying to get into this sort of math for a while, and could never fully understand it that well.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 7, 2016
  12. Offline

    desht

    I actually worked from this page: http://stackoverflow.com/questions/5666222/3d-line-plane-intersection

    The maths are somewhat complex and require a good understanding of 3d geometry - to be honest, slightly beyond my ability to explain well :) But the two replies in the linked page above have links to some good data (I adapted the isectLinePlane() method in my code from the Blender code and the Python snippet mentioned there).
     
  13. Offline

    Maurdekye

    desht Hm, thanks.

    desht player.getLastTwoTargetBlocks() is a deprecated method; what exactly does it do? Could I recreate it with a BlockIterator?

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 7, 2016
  14. Offline

    desht

    It's only deprecated because it takes a HashSet<Byte> parameter, i.e. numeric block id's. Since there isn't a replacement in Bukkit for that yet, just use it. It returns the furthest two targeted blocks for the player, i.e. the block the player has targeted and the block immediately before - I use it to work out which face of the block is targeted.
     
  15. Offline

    Maurdekye

    desht Oh, I've already done that with a BlockIterator to do exactly the same thing. Thanks for your help.

    Edit: Here are the method(s) i've created to supplant the deprecated one;
    Code:java
    1. public List<Block> getLastTwoTargets(Player ply, int range, List<Material> ignored) {
    2. BlockIterator iter = new BlockIterator(ply, Math.max(2 ,range));
    3. ArrayList<Block> lastTwo = new ArrayList<>();
    4. lastTwo.addAll(Arrays.asList(iter.next(), iter.next()));
    5. while (iter.hasNext()) {
    6. Block current = iter.next();
    7. lastTwo.set(1, lastTwo.get(0));
    8. lastTwo.set(0, current);
    9. if (!ignored.contains(current.getType())) break;
    10. }
    11. return lastTwo;
    12. }
    13. public List<Block> getLastTwoTargets(Player ply, int range) {
    14. return getLastTwoTargets(ply, range, Arrays.asList(Material.AIR));
    15. }


    Unfortunately, I'm going to have to leave this thread as unsolved, because my implementation of those methods doesn't seem to work. If I look too closely at a block, then I get null errors, and from what I can tell, the point they return isn't very accurate. Here's what I have so far;
    Code:java
    1. // My getTargetPoint method;
    2. public Location getTargetPoint(Player ply, List<Material> ignored, int range) {
    3. List<Block> lastTwo = getLastTwoTargets(ply, ignored, range);
    4. BlockFace face = lastTwo.get(1).getFace(lastTwo.get(0));
    5. if (face == null) return null;
    6. Vector planePoint = lastTwo.get(0).getLocation().toVector();
    7.  
    8. switch(face) {
    9. case EAST: planePoint.setX(planePoint.getX() + 1);
    10. case SOUTH: planePoint.setZ(planePoint.getZ() + 1);
    11. case UP: planePoint.setY(planePoint.getY() + 1);
    12. }
    13.  
    14. Vector planeNormal = new Vector(face.getModX(), face.getModY(), face.getModZ());
    15. Vector lineOne = ply.getEyeLocation().toVector();
    16. Vector lineTwo = lineOne.clone().add(ply.getLocation().getDirection());
    17.  
    18. Vector finalPoint = lineIntersectPlane(lineOne, lineTwo, planePoint, planeNormal);
    19. return new Location(ply.getWorld(), finalPoint.getX(), finalPoint.getY(), finalPoint.getZ());
    20. }
    21. public Location getTargetPoint(Player ply, int range) {
    22. return getTargetPoint(ply, Arrays.asList(Material.AIR), range);
    23. }
    24.  
    25. // My lineIntersectPlane method;
    26. public Vector lineIntersectPlane(Vector lineOne, Vector lineTwo, Vector planePoint, Vector planeNormal, double epsilon) {
    27. Vector u = lineTwo.clone().subtract(lineOne);
    28. Vector w = lineOne.clone().subtract(planePoint);
    29. double dot = planeNormal.dot(u);
    30. if (Math.abs(dot) > epsilon) {
    31. u.multiply(-planeNormal.dot(w) / dot);
    32. return lineOne.clone().add(u);
    33. } else return null;
    34. }
    35. public Vector lineIntersectPlane(Vector lineOne, Vector lineTwo, Vector planePoint, Vector planeNormal) {
    36. return lineIntersectPlane(lineOne, lineTwo, planePoint, planeNormal, 0.0000001);
    37. }


    I'm not sure if i'm doing something wrong, or i'm just not using the data correctly. But if someone could advise, it would be greatly appreciated.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 7, 2016
  16. Offline

    desht

    Yeah, getLastTwoTargetBlocks() could only return one element if you're really close to the block. Can't remember offhand what I did about that (the code is very much a proof of concept to see how well it could work), but if you're so close to the targeted block that only a single block is returned, it should be fairly safe to derive the block face from the player's facing direction or the player's eye location relative to the block's location.

    My (limited) testing got pretty accurate values for the intersection but it's certainly possible that I didn't cover some corner cases. In any case, calculating the line/plane intersection is the right way to go about this.
     
  17. Offline

    Maurdekye

    desht Well, I don't have the most accurate method of testing visually for the exact intersection point, so that might be partially a problem. Also, I'll look into fixing my getLastTwo method; thanks for the advice.
     
  18. Offline

    desht

    Maurdekye may be of interest to you - I just created https://github.com/Bukkit/Bukkit/pull/1059 which if pulled will resolve this problem completely by adding this functionality into Bukkit. There is some work to do on the PR before it gets pulled, but I'm taking the fact that it wasn't closed immediately as a good sign :)
     
    Lactem likes this.
  19. Offline

    Maurdekye

    desht Ah, nice. Good on you.
     
Thread Status:
Not open for further replies.

Share This Page