Solved Drawing Line of Blocks Between Two Locations

Discussion in 'Plugin Development' started by MrFigg, Jan 18, 2012.

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

    MrFigg

    Hi, I'm writing a plugin to make animated fireworks made of wool. I'm having trouble with drawing the lines of blocks between two locations for the rockets. Searching the forums I've only found 1 thread related to this, and it only involved drawing on a 2d plane. And wasn't answered clearly. Googling led me to plenty of ways to find a point on a 3d line, but none for drawing the whole line.

    Here's the animate function in my projectile class that I have so far:
    Code:
    public void animate() {
        if(first) {
            plugin.animator.addBlocks.put(new Location(location.getWorld(), location.getBlockX(), location.getBlockY(), location.getBlockZ()), DyeColor.RED);
            first = false;
        } else {
            speed.setY(speed.getY()-gravity);
            Location newLocation = location.clone();
            newLocation.add(speed);
            if(newLocation.getY()>MAX_Y) newLocation.setY(MAX_Y);
            if(newLocation.getY()<MIN_Y) newLocation.setY(MIN_Y);
            int startX, startY, startZ, endX, endY, endZ;
            if(location.getBlockX()<newLocation.getBlockX()) {
                startX = location.getBlockX();
                endX = newLocation.getBlockX();
            } else {
                startX = newLocation.getBlockX();
                endX = location.getBlockX();
            }
            if(location.getBlockY()<newLocation.getBlockY()) {
                startY = location.getBlockY();
                endY = newLocation.getBlockY();
            } else {
                startY = newLocation.getBlockY();
                endY = location.getBlockY();
            }
            if(location.getBlockZ()<newLocation.getBlockZ()) {
                startZ = location.getBlockZ();
                endZ = newLocation.getBlockZ();
            } else {
                startZ = newLocation.getBlockZ();
                endZ = location.getBlockZ();
            }
            // ************* Part I need help with *************
            for(int x = startX; x <= endX; x++) {
                for(int y = startY; y <= endY; y++) {
                    for(int z = startZ; z<= endZ; z++) {
                        plugin.animator.addBlocks.put(new Location(location.getWorld(), x, y, z), DyeColor.RED);
                    }
                }
            }
            // *************************************************
            location = newLocation;
            if(speed.getY()<-1) explode();
        }
    }
    'speed' is a Vector, and 'gravity' is a double. I think everything else should be self-explanatory.

    Of course all this does is make ugly rectangles. Does anyone know of the proper way to go about drawing lines?

    Also, as I'm sure I will have to figure this out eventually, does anyone know of a good way to light up blocks, without placing light source blocks? And hopefully without using the obfuscated functions in craftbukkit? Not too hopeful about this one but thought I'd ask, as it's something I really wanted eventually.

    Anyway, thanks for any help I get. If the plugin turns out to not be made of fail-sauce when it's done, I'll link to it here.


    EDIT:
    SOLUTION

    Hey everyone, here's the solution I ended up using:
    Code:
        public void animate() {
            if(first) {
                plugin.animator.addBlocks.put(new Location(location.getWorld(), location.getBlockX(), location.getBlockY(), location.getBlockZ()), DyeColor.RED);
                first = false;
            } else {
                speed.setY(speed.getY()-gravity);
                Location newLocation = location.clone();
                newLocation.add(speed);
                if(newLocation.getY()>MAX_Y) newLocation.setY(MAX_Y);
                if(newLocation.getY()<MIN_Y) newLocation.setY(MIN_Y);
                LocationIterator blocksToAdd = new LocationIterator(location.getWorld(), location.toVector(), new Vector(newLocation.getBlockX()-location.getBlockX(), newLocation.getBlockY()-location.getBlockY(), newLocation.getBlockZ()-location.getBlockZ()), 0, (int) Math.floor(location.distance(newLocation)));
                Location blockToAdd;
                while(blocksToAdd.hasNext()) {
                    blockToAdd = blocksToAdd.next();
                    plugin.animator.addBlocks.put(new Location(blockToAdd.getWorld(), blockToAdd.getBlockX(), blockToAdd.getBlockY(), blockToAdd.getBlockZ()), DyeColor.RED);
                }
                location = newLocation;
                if(speed.getY()<-1) explode();
            }
        }
    And here's the LocationIterator class:
    Code:
    import java.util.Iterator;
    import java.util.NoSuchElementException;
     
    import org.bukkit.Location;
    import org.bukkit.World;
    import org.bukkit.block.BlockFace;
    import org.bukkit.entity.LivingEntity;
    import org.bukkit.util.Vector;
     
    public class LocationIterator implements Iterator<Location>{
        private final World world;
        @SuppressWarnings("unused")
        private final int maxDistance;
       
        private static final int gridSize = 1 << 24;
       
        private boolean end = false;
       
        private Location[] locationQueue = new Location[3];
        private int currentLocation = 0;
        private int currentDistance = 0;
        private int maxDistanceInt;
       
        private int secondError;
        private int thirdError;
       
        private int secondStep;
        private int thirdStep;
       
        private BlockFace mainFace;
        private BlockFace secondFace;
        private BlockFace thirdFace;
       
        public LocationIterator(World world, Vector start, Vector direction, double yOffset, int maxDistance) {
            this.world = world;
            this.maxDistance = maxDistance;
           
            Vector startClone = start.clone();
           
            startClone.setY(startClone.getY() + yOffset);
           
            currentDistance = 0;
           
            double mainDirection = 0;
            double secondDirection = 0;
            double thirdDirection = 0;
           
            double mainPosition = 0;
            double secondPosition = 0;
            double thirdPosition = 0;
           
            Location startLocation = new Location(this.world, (int) Math.floor(startClone.getX()), (int) Math.floor(startClone.getY()), (int) Math.floor(startClone.getZ()));
            mainFace = getXFace(direction);
            mainDirection = getXLength(direction);
            mainPosition = getXPosition(direction, startClone, startLocation);
           
            secondFace = getYFace(direction);
            secondDirection = getYLength(direction);
            secondPosition = getYPosition(direction, startClone, startLocation);
           
            thirdFace = getZFace(direction);
            thirdDirection = getZLength(direction);
            thirdPosition = getZPosition(direction, startClone, startLocation);
            if(getYLength(direction) > mainDirection) {
                mainFace = getYFace(direction);
                mainDirection = getYLength(direction);
                mainPosition = getYPosition(direction, startClone, startLocation);
               
                secondFace = getZFace(direction);
                secondDirection = getZLength(direction);
                secondPosition = getZPosition(direction, startClone, startLocation);
               
                thirdFace = getXFace(direction);
                thirdDirection = getXLength(direction);
                thirdPosition = getXPosition(direction, startClone, startLocation);
            }
            if(getZLength(direction) > mainDirection) {
                mainFace = getZFace(direction);
                mainDirection = getZLength(direction);
                mainPosition = getZPosition(direction, startClone, startLocation);
               
                secondFace = getXFace(direction);
                secondDirection = getXLength(direction);
                secondPosition = getXPosition(direction, startClone, startLocation);
               
                thirdFace = getYFace(direction);
                thirdDirection = getYLength(direction);
                thirdPosition = getYPosition(direction, startClone, startLocation);
            }
           
            // trace line backwards to find intercept with plane perpendicular to the main axis
           
            double d = mainPosition / mainDirection; // how far to hit face behind
            double secondd = secondPosition - secondDirection * d;
            double thirdd = thirdPosition - thirdDirection * d;
           
            // Guarantee that the ray will pass though the start block.
            // It is possible that it would miss due to rounding
            // This should only move the ray by 1 grid position
            secondError = (int) (Math.floor(secondd * gridSize));
            secondStep = (int) (Math.round(secondDirection / mainDirection * gridSize));
            thirdError = (int) (Math.floor(thirdd * gridSize));
            thirdStep = (int) (Math.round(thirdDirection / mainDirection * gridSize));
           
            if(secondError + secondStep <= 0) {
                secondError = -secondStep + 1;
            }
           
            if(thirdError + thirdStep <= 0) {
                thirdError = -thirdStep + 1;
            }
           
            Location lastLocation;
           
            lastLocation = getRelativeLocation(startLocation, reverseFace(mainFace));
     
            if(secondError < 0) {
                secondError += gridSize;
                lastLocation = getRelativeLocation(lastLocation, reverseFace(secondFace));
            }
     
            if(thirdError < 0) {
                thirdError += gridSize;
                lastLocation = getRelativeLocation(lastLocation, reverseFace(thirdFace));
            }
     
            // This means that when the variables are positive, it means that the coord=1 boundary has been crossed
            secondError -= gridSize;
            thirdError -= gridSize;
     
            locationQueue[0] = lastLocation;
            currentLocation = -1;
     
            scan();
     
            boolean startLocationFound = false;
     
            for (int cnt = currentLocation; cnt >= 0; cnt--) {
                if(locationEquals(locationQueue[cnt], startLocation)) {
                    currentLocation = cnt;
                    startLocationFound = true;
                    break;
                }
            }
     
            if(!startLocationFound) {
                throw new IllegalStateException("Start location missed in LocationIterator");
            }
     
            // Calculate the number of planes passed to give max distance
            maxDistanceInt = (int) Math.round(maxDistance / (Math.sqrt(mainDirection * mainDirection + secondDirection * secondDirection + thirdDirection * thirdDirection) / mainDirection));
     
        }
     
        private boolean locationEquals(Location a, Location b) {
            return a.getBlockX() == b.getBlockX() && a.getBlockY() == b.getBlockY() && a.getBlockZ() == b.getBlockZ();
        }
     
        private BlockFace reverseFace(BlockFace face) {
            switch(face) {
            case UP:
                return BlockFace.DOWN;
     
            case DOWN:
                return BlockFace.UP;
     
            case NORTH:
                return BlockFace.SOUTH;
     
            case SOUTH:
                return BlockFace.NORTH;
     
            case EAST:
                return BlockFace.WEST;
     
            case WEST:
                return BlockFace.EAST;
     
            default:
                return null;
            }
        }
     
        private BlockFace getXFace(Vector direction) {
            return ((direction.getX() > 0) ? BlockFace.SOUTH : BlockFace.NORTH);
        }
     
        private BlockFace getYFace(Vector direction) {
            return ((direction.getY() > 0) ? BlockFace.UP : BlockFace.DOWN);
        }
     
        private BlockFace getZFace(Vector direction) {
            return ((direction.getZ() > 0) ? BlockFace.WEST : BlockFace.EAST);
        }
     
        private double getXLength(Vector direction) {
            return Math.abs(direction.getX());
        }
     
        private double getYLength(Vector direction) {
            return Math.abs(direction.getY());
        }
     
        private double getZLength(Vector direction) {
            return Math.abs(direction.getZ());
        }
     
        private double getPosition(double direction, double position, int locationPosition) {
            return direction > 0 ? (position - locationPosition) : (locationPosition + 1 - position);
        }
     
        private double getXPosition(Vector direction, Vector position, Location location) {
            return getPosition(direction.getX(), position.getX(), location.getBlockX());
        }
     
        private double getYPosition(Vector direction, Vector position, Location location) {
            return getPosition(direction.getY(), position.getY(), location.getBlockY());
        }
     
        private double getZPosition(Vector direction, Vector position, Location location) {
            return getPosition(direction.getZ(), position.getZ(), location.getBlockZ());
        }
     
        public LocationIterator(Location loc, double yOffset, int maxDistance) {
            this(loc.getWorld(), loc.toVector(), loc.getDirection(), yOffset, maxDistance);
        }
     
        public LocationIterator(Location loc, double yOffset) {
            this(loc.getWorld(), loc.toVector(), loc.getDirection(), yOffset, 0);
        }
     
        public LocationIterator(Location loc) {
            this(loc, 0D);
        }
     
        public LocationIterator(LivingEntity entity, int maxDistance) {
            this(entity.getLocation(), entity.getEyeHeight(), maxDistance);
        }
     
        public LocationIterator(LivingEntity entity) {
            this(entity, 0);
        }
       
        public Location getRelativeLocation(Location location, BlockFace face) {
            switch (face) {
            case UP:
                return location.clone().add(0, 1, 0);
     
            case DOWN:
                return location.clone().add(0, -1, 0);
     
            case NORTH:
                return location.clone().add(-1, 0, 0);
     
            case SOUTH:
                return location.clone().add(1, 0, 0);
     
            case EAST:
                return location.clone().add(0, 0, -1);
     
            case WEST:
                return location.clone().add(0, 0, 1);
     
            default:
                return null;
            }
        }
       
        public boolean hasNext() {
            scan();
            return currentLocation != -1;
        }
     
        public Location next() {
            scan();
            if(currentLocation <= -1) {
                throw new NoSuchElementException();
            } else {
                return locationQueue[currentLocation--];
            }
        }
     
        public void remove() {
            throw new UnsupportedOperationException("[LocationIterator] doesn't support location removal");
        }
     
        private void scan() {
            if(currentLocation >= 0) {
                return;
            }
            //if(maxDistance != 0 && currentDistance > maxDistanceInt) {
            if(currentDistance > maxDistanceInt) {
                end = true;
                return;
            }
            if(end) {
                return;
            }
     
            currentDistance++;
     
            secondError += secondStep;
            thirdError += thirdStep;
     
            if(secondError > 0 && thirdError > 0) {
                locationQueue[2] = getRelativeLocation(locationQueue[0], mainFace);
                if(((long) secondStep) * ((long) thirdError) < ((long) thirdStep) * ((long) secondError)) {
                    locationQueue[1] = getRelativeLocation(locationQueue[2], secondFace);
                    locationQueue[0] = getRelativeLocation(locationQueue[1], thirdFace);
                } else {
                    locationQueue[1] = getRelativeLocation(locationQueue[2], thirdFace);
                    locationQueue[0] = getRelativeLocation(locationQueue[1], secondFace);
                }
                thirdError -= gridSize;
                secondError -= gridSize;
                currentLocation = 2;
                return;
            } else if(secondError > 0) {
                locationQueue[1] = getRelativeLocation(locationQueue[0], mainFace);
                locationQueue[0] = getRelativeLocation(locationQueue[1], secondFace);
                secondError -= gridSize;
                currentLocation = 1;
                return;
            } else if(thirdError > 0) {
                locationQueue[1] = getRelativeLocation(locationQueue[0], mainFace);
                locationQueue[0] = getRelativeLocation(locationQueue[1], thirdFace);
                thirdError -= gridSize;
                currentLocation = 1;
                return;
            } else {
                locationQueue[0] = getRelativeLocation(locationQueue[0], mainFace);
                currentLocation = 0;
                return;
            }
        }
    }
    It's pretty much the BlockIterator class rewritten so that it only uses Locations. Of course credit to the Bukkit team for almost all of the code.

    The reason I ended up rewriting the BlockIterator class as LocationIterator was to avoid unneeded lag, as my Animator class handles all block setting based on locations anyway. Note that while you will probably use the BlockIterator class, it has a problem if you try to set the distance = 0.

    Thanks a ton to Father Of Time for your help.
     
    Phasesaber likes this.
  2. Offline

    Shamebot

    Do you know the BlockIterator class?
     
  3. Offline

    Father Of Time

    As suggested by the previous poster, BlockIterators are likely your best bet.

    http://jd.bukkit.org/doxygen/d6/d62...erator.html#a87b929eae0296e64638530ee9db7a465

    In short:
    1) get point a
    2) get point b
    4) get distance from point A to point B
    3) get direction from point a to point b
    4) convert direction to vector
    5) use a block iterator to get a set of blocks that are in a direct line from point A to point B
    6) iterate through the block set and perform the action you desire on each block, something like (pseudo code):

    Code:
    Block block = itr.Next();
    block.setMaterial( Material.WOOL );
    Hope this helps clear things up, good luck!
     
  4. Offline

    MrFigg

    Cool, this looks like exactly what I needed. Thanks Shambot/Father Of Time. Now I just gotta figure out how to get direction from point A to point B. I suck at understanding pitch & yaw math.

    EDIT:
    Wait a minute, I don't have to calculate anything do I, I can just give the newLocation as a vector. Cool. I'll post the new code if it works.
     
  5. Offline

    Father Of Time

    No, unfortunately I don't believe this will work. The vector argument you pass is used to determine what direction to travel from the origin of the trace, where the pointB location converted to a Vector takes the X, Y, Z, Yaw and Pitch to calculate a new Vector, and since the Locations Yaw and Pitch are determined at the time you created the location variable (it depends on what direction you are facing when you record the location, if its done programatically then it will be 0 yaw and 0 pitch).

    Unfortunately you need to determine the direction vector from the PointA and PointB variables. :( I could have sworn that Either Vector or Location had a "getDirection( Location )" function built into it to get the direction a second location is from the first, but I've been searching for 15 minutes to post it for you and just can't seem to find it!

    If I find anything I will let you know, but I'm fairly sure your suggested method won't function as expected. Good luck with your search, I hope we find the answer quickly! :D
     
  6. Offline

    MrFigg

    Yea, trying to use newLocation.toVector() ended up making my test server freeze after a few passes of the animator. Not to mention they never pointed in the right direction. Off to google I guess.

    EDIT:
    So the solution was pretty simple really.
    Code:
    BlockIterator blocksToAdd = new BlockIterator(location.getWorld(), location.toVector(), new Vector(newLocation.getBlockX()-location.getBlockX(), newLocation.getBlockY()-location.getBlockY(), newLocation.getBlockZ()-location.getBlockZ()), 0, (int) Math.floor(location.distanceSquared(newLocation)));
    Location blockToAdd;
    while(blocksToAdd.hasNext()) {
        blockToAdd = blocksToAdd.next().getLocation();
        plugin.animator.addBlocks.put(new Location(blockToAdd.getWorld(), blockToAdd.getBlockX(), blockToAdd.getBlockY(), blockToAdd.getBlockZ()), DyeColor.RED);
    }
    Now, while this does work sometimes, I keep having problems with the BlockIterator class. Sometimes it gets a null pointer error, and other times my test server freezes. I think it has something to do with rapidly getting block data. So I decided to make a LocationIterator class, sense I don't need the block data anyway. Once that's done and everything works I'll post it here, in case anyone is interested in using it.

    Hey, just wanted to make a post so everyone knew this got solved. I'm going to edit the original post now to include my solution.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 23, 2016
Thread Status:
Not open for further replies.

Share This Page