/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.utils.coords; import java.util.ArrayList; import java.util.Collection; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.ThreadSingletons; import org.bukkit.Location; import org.bukkit.World; /** * Chunk related utilities. */ public final class ChunkUtils { private ChunkUtils() {} private static final ThreadSingletons<CacheCoords> CACHE_COORDS = new ThreadSingletons<>(new ThreadSingletons.ISingletonFactory<CacheCoords>() { @Override public CacheCoords create(Thread thread) { return new CacheCoords(); } }); private static ICoords2Di[] CHUNK_CORNER_BLOCKS = new ICoords2Di[] { new Coords2Di(0, 0), new Coords2Di(0, 15), new Coords2Di(15, 15), new Coords2Di(15, 0) }; /** * Get chunk coordinates from a location. * * @param location The location. */ public static Coords2Di getChunkCoords(Location location) { PreCon.notNull(location, "location"); int x = (int)Math.floor(location.getX() / 16); int z = (int)Math.floor(location.getZ() / 16); return new Coords2Di(x, z); } /** * Get chunk coordinates from a location and copy the result into an output * {@link MutableCoords2Di}. * * @param location The location. * @param output The output {@link MutableCoords2Di}. * * @return The output {@link MutableCoords2Di}. */ public static MutableCoords2Di getChunkCoords(Location location, MutableCoords2Di output) { PreCon.notNull(location, "location"); PreCon.notNull(output, "output"); int x = (int)Math.floor(location.getX() / 16); int z = (int)Math.floor(location.getZ() / 16); output.setX(x); output.setZ(z); return output; } /** * Determine if the {@link org.bukkit.Chunk} at the specified {@link org.bukkit.Location} and all * chunks within the specified radius around it are loaded. * * @param location The {@link org.bukkit.Location} to check. * @param radius The radius to check. * * @return True if all chunks within the radius are loaded, otherwise false. */ public static boolean isNearbyChunksLoaded(Location location, int radius) { PreCon.notNull(location, "location"); PreCon.positiveNumber(radius, "radius"); Coords2Di chunkCoords = getChunkCoords(location, CACHE_COORDS.get().chunk); return isNearbyChunksLoaded(location.getWorld(), chunkCoords.getX(), chunkCoords.getZ(), radius); } /** * Determine if the {@link org.bukkit.Chunk} at the specified coordinates and all * chunks within the specified radius around it are loaded. * * @param world The {@link org.bukkit.World} to check in. * @param chunkX The X coordinates of the chunk. * @param chunkZ The Z coordinates of the chunk. * @param radius The radius to check. * * @return True if all chunks within the radius are loaded, otherwise false. */ public static boolean isNearbyChunksLoaded(World world, int chunkX, int chunkZ, int radius) { PreCon.notNull(world, "world"); PreCon.positiveNumber(radius, "radius"); int startX = chunkX - radius; int startZ = chunkZ - radius; int endX = chunkX + radius; int endZ = chunkZ + radius; for (int x = startX; x <= endX; x++) { for (int z = startZ; z <= endZ; z++) { if (!world.isChunkLoaded(x, z)) { return false; } } } return true; } /** * Get all chunks that contain the location and the specified radius around it. * * @param location The location. * @param blockRadius The block radius around the location. */ public static Collection<IChunkCoords> getChunksInRadius(Location location, int blockRadius) { return getChunksInRadius(location, blockRadius, new ArrayList<IChunkCoords>((blockRadius >> 4) * (blockRadius >> 4))); } /** * Get all chunks that contain the location and the specified radius around it and * add the the specified output collection. * * @param location The location. * @param blockRadius The block radius around the location. * @param output The output collection. * * @return The output collection. */ public static <T extends Collection<IChunkCoords>> T getChunksInRadius(Location location, int blockRadius, T output) { PreCon.notNull(location, "location"); PreCon.notNull(location.getWorld(), "location world"); PreCon.positiveNumber(blockRadius, "blockRadius"); PreCon.notNull(output, "output"); int chunkRadius = (blockRadius >> 4) + 1; int blockRadiusSquared = blockRadius * blockRadius; int chunkX = location.getBlockX() >> 4; int chunkZ = location.getBlockZ() >> 4; CacheCoords cache = CACHE_COORDS.get(); MutableChunkCoords chunkCoords = new MutableChunkCoords(); for (int x=-chunkRadius; x <= chunkRadius; x++) { for (int z=-chunkRadius; z <= chunkRadius; z++) { setChunkCoords(chunkCoords, location.getWorld(), chunkX + x, chunkZ + z); if (x == 0 && z == 0) { output.add(chunkCoords); chunkCoords = new MutableChunkCoords(); continue; } double distance = getChunkDistanceSquared(location, chunkCoords, cache); if (distance <= blockRadiusSquared) { output.add(chunkCoords); chunkCoords = new MutableChunkCoords(); } } } return output; } /** * Get the corner of a chunk that is closest to the specified source location. * * @param chunkCoords The coords of the chunk to check. * @param source The source location. * * @return 1 of 4 possible singleton values. The coords returned represents the coordinate * of the corner within the chunk relative to the chunk. */ public static ICoords2Di getClosestChunkCorner(ICoords2Di chunkCoords, Location source) { PreCon.notNull(chunkCoords, "chunkCoords"); PreCon.notNull(source, "source"); PreCon.notNull(source.getWorld(), "source world"); return getClosestChunkCorner(chunkCoords, source, CACHE_COORDS.get()); } /** * Get the distance squared from the source location to the closest corner of a specified chunk. * * @param chunkCoords The coords of the chunk to check. * @param source The source location. */ public static double getCornerDistanceSquared(Location source, ICoords2Di chunkCoords) { PreCon.notNull(chunkCoords, "chunkCoords"); PreCon.notNull(source, "source"); PreCon.notNull(source.getWorld(), "source world"); return getCornerDistanceSquared(source, chunkCoords, CACHE_COORDS.get()); } /** * Get the location within a specified chunk that is closest to the specified * source location. * * @param source The source location. * @param chunkCoords The chunk boundary. */ public static Location getClosestChunkLocation(Location source, ICoords2Di chunkCoords) { return getClosestChunkLocation(source, chunkCoords, new Location(null, 0, 0, 0)); } /** * Get the location within a specified chunk that is closest to the specified * source location. * * @param source The source location. * @param chunkCoords The chunk boundary. * @param output The output location. * * @return The output location; */ public static Location getClosestChunkLocation(Location source, ICoords2Di chunkCoords, Location output) { PreCon.notNull(source, "source"); PreCon.notNull(source.getWorld(), "source world"); PreCon.notNull(chunkCoords, "chunkCoords"); PreCon.notNull(output, "output"); return getClosestChunkLocation(source, chunkCoords, output, CACHE_COORDS.get()); } /** * Get the distance to the closest block within a specified chunk from the specified * source location. * * @param source The source location. * @param chunkCoords The chunk boundary. */ public static int getChunkDistanceSquared(Location source, ICoords2Di chunkCoords) { PreCon.notNull(source, "source"); PreCon.notNull(source.getWorld(), "source world"); PreCon.notNull(chunkCoords, "chunkCoords"); return getChunkDistanceSquared(source, chunkCoords, CACHE_COORDS.get()); } private static ICoords2Di getClosestChunkCorner(ICoords2Di chunkCoords, Location source, CacheCoords coords) { ICoords2Di closest = null; double closestDist = 0D; ICoords2Di sourceCoords = set2DCoords(coords.source, source.getBlockX(), source.getBlockZ()); int chunkWorldX = chunkCoords.getX() * 16; int chunkWorldZ = chunkCoords.getZ() * 16; for (ICoords2Di corner : CHUNK_CORNER_BLOCKS) { ICoords2Di cornerLoc = set2DCoords(coords.target, chunkWorldX + corner.getX(), chunkWorldZ + corner.getZ()); double distance = Coords2Di.distanceSquared(sourceCoords, cornerLoc); if (closest == null || closestDist > distance) { closest = corner; closestDist = distance; } } return closest; } private static double getCornerDistanceSquared(Location source, ICoords2Di chunkCoords, CacheCoords cache) { double closestDist = -1D; ICoords2Di sourceCoords = set2DCoords(cache.source, source.getBlockX(), source.getBlockZ()); int chunkWorldX = chunkCoords.getX() * 16; int chunkWorldZ = chunkCoords.getZ() * 16; for (ICoords2Di corner : CHUNK_CORNER_BLOCKS) { ICoords2Di cornerLoc = set2DCoords(cache.target, chunkWorldX + corner.getX(), chunkWorldZ + corner.getZ()); double distance = Coords2Di.distanceSquared(sourceCoords, cornerLoc); if (closestDist < 0 || closestDist > distance) { closestDist = distance; } } return closestDist; } private static int getChunkDistanceSquared(Location source, ICoords2Di chunkCoords, CacheCoords cache) { int sourceChunkX = source.getBlockX() >> 4; int sourceChunkZ = source.getBlockZ() >> 4; int chunkX = chunkCoords.getX(); int chunkZ = chunkCoords.getZ(); if (chunkX == sourceChunkX && chunkZ == sourceChunkZ) { return 0; } int faceX = sourceChunkX > chunkX ? 15 : sourceChunkX == chunkX ? -1 : 0; int faceZ = sourceChunkZ > chunkZ ? 15 : sourceChunkZ == chunkZ ? -1 : 0; int arraySize = 0; if (faceX != -1) { for (int z = 0; z < 16; z++, arraySize++) { cache.chunksX[arraySize] = faceX; cache.chunksZ[arraySize] = z; } } if (faceZ != -1) { for (int x = 0; x < 16; x++, arraySize++) { cache.chunksX[arraySize] = x; cache.chunksZ[arraySize] = faceZ; } } int closest = -1; for (int i=0; i < arraySize; i++) { int x = cache.chunksX[i]; int z = cache.chunksZ[i]; int deltaX = (chunkX + x) - source.getBlockX(); int deltaZ = (chunkZ + z) - source.getBlockZ(); int distance = deltaX * deltaX + deltaZ * deltaZ; if (closest == -1 || distance < closest) { closest = distance; } } return closest; } private static Location getClosestChunkLocation(Location source, ICoords2Di chunkCoords, Location output, CacheCoords cache) { PreCon.notNull(source, "source"); PreCon.notNull(source.getWorld(), "source world"); PreCon.notNull(chunkCoords, "chunkCoords"); PreCon.notNull(output, "output"); int sourceChunkX = source.getBlockX() >> 4; int sourceChunkZ = source.getBlockZ() >> 4; int chunkX = chunkCoords.getX(); int chunkZ = chunkCoords.getZ(); if (chunkX == sourceChunkX && chunkZ == sourceChunkZ) { return LocationUtils.copy(source, output); } int faceX = sourceChunkX > chunkX ? 15 : sourceChunkX == chunkX ? -1 : 0; int faceZ = sourceChunkZ > chunkZ ? 15 : sourceChunkZ == chunkZ ? -1 : 0; int arraySize = 0; if (faceX != -1) { for (int z = 0; z < 16; z++, arraySize++) { cache.chunksX[arraySize] = faceX; cache.chunksZ[arraySize] = z; } } if (faceZ != -1) { for (int x = 0; x < 16; x++, arraySize++) { cache.chunksX[arraySize] = x; cache.chunksZ[arraySize] = faceZ; } } int closest = -1; int closestX = 0; int closestZ = 0; for (int i=0; i < arraySize; i++) { int x = cache.chunksX[i]; int z = cache.chunksZ[i]; int deltaX = (chunkX + x) - source.getBlockX(); int deltaZ = (chunkZ + z) - source.getBlockZ(); int distance = deltaX * deltaX + deltaZ * deltaZ; if (closest == -1 || distance < closest) { closest = distance; closestX = x; closestZ = z; } } output.setWorld(source.getWorld()); output.setX(chunkX + closestX); output.setY(source.getBlockY()); output.setZ(chunkZ + closestZ); output.setYaw(0f); output.setPitch(0f); return output; } private static class CacheCoords { final MutableCoords2Di source = new MutableCoords2Di(); final MutableCoords2Di target = new MutableCoords2Di(); final MutableCoords2Di chunk = new MutableCoords2Di(); final int[] chunksX = new int[34]; final int[] chunksZ = new int[34]; } private static ICoords2Di set2DCoords(MutableCoords2Di coords, int x, int z) { coords.setX(x); coords.setZ(z); return coords; } private static ICoords2Di setChunkCoords(MutableChunkCoords coords, World world, int x, int z) { coords.setWorld(world); coords.setX(x); coords.setZ(z); return coords; } }