package net.minecraft.world.level.block.state.pattern; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.util.function.Predicate; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Vec3i; import net.minecraft.world.level.LevelReader; import org.jetbrains.annotations.Nullable; public class BlockPattern { private final Predicate[][][] pattern; private final int depth; private final int height; private final int width; public BlockPattern(Predicate[][][] pattern) { this.pattern = pattern; this.depth = pattern.length; if (this.depth > 0) { this.height = pattern[0].length; if (this.height > 0) { this.width = pattern[0][0].length; } else { this.width = 0; } } else { this.height = 0; this.width = 0; } } public int getDepth() { return this.depth; } public int getHeight() { return this.height; } public int getWidth() { return this.width; } @VisibleForTesting public Predicate[][][] getPattern() { return this.pattern; } @Nullable @VisibleForTesting public BlockPattern.BlockPatternMatch matches(LevelReader level, BlockPos pos, Direction finger, Direction thumb) { LoadingCache loadingCache = createLevelCache(level, false); return this.matches(pos, finger, thumb, loadingCache); } /** * Checks that the given pattern & rotation is at the block coordinates. */ @Nullable private BlockPattern.BlockPatternMatch matches(BlockPos pos, Direction finger, Direction thumb, LoadingCache cache) { for (int i = 0; i < this.width; i++) { for (int j = 0; j < this.height; j++) { for (int k = 0; k < this.depth; k++) { if (!this.pattern[k][j][i].test(cache.getUnchecked(translateAndRotate(pos, finger, thumb, i, j, k)))) { return null; } } } } return new BlockPattern.BlockPatternMatch(pos, finger, thumb, cache, this.width, this.height, this.depth); } /** * Calculates whether the given world position matches the pattern. Warning, fairly heavy function. * @return a BlockPatternMatch if found, null otherwise. */ @Nullable public BlockPattern.BlockPatternMatch find(LevelReader level, BlockPos pos) { LoadingCache loadingCache = createLevelCache(level, false); int i = Math.max(Math.max(this.width, this.height), this.depth); for (BlockPos blockPos : BlockPos.betweenClosed(pos, pos.offset(i - 1, i - 1, i - 1))) { for (Direction direction : Direction.values()) { for (Direction direction2 : Direction.values()) { if (direction2 != direction && direction2 != direction.getOpposite()) { BlockPattern.BlockPatternMatch blockPatternMatch = this.matches(blockPos, direction, direction2, loadingCache); if (blockPatternMatch != null) { return blockPatternMatch; } } } } } return null; } public static LoadingCache createLevelCache(LevelReader level, boolean forceLoad) { return CacheBuilder.newBuilder().build(new BlockPattern.BlockCacheLoader(level, forceLoad)); } /** * Offsets the position of pos in the direction of finger and thumb facing by offset amounts, follows the right-hand rule for cross products (finger, thumb, palm) * * @return a new BlockPos offset in the facing directions */ protected static BlockPos translateAndRotate(BlockPos pos, Direction finger, Direction thumb, int palmOffset, int thumbOffset, int fingerOffset) { if (finger != thumb && finger != thumb.getOpposite()) { Vec3i vec3i = new Vec3i(finger.getStepX(), finger.getStepY(), finger.getStepZ()); Vec3i vec3i2 = new Vec3i(thumb.getStepX(), thumb.getStepY(), thumb.getStepZ()); Vec3i vec3i3 = vec3i.cross(vec3i2); return pos.offset( vec3i2.getX() * -thumbOffset + vec3i3.getX() * palmOffset + vec3i.getX() * fingerOffset, vec3i2.getY() * -thumbOffset + vec3i3.getY() * palmOffset + vec3i.getY() * fingerOffset, vec3i2.getZ() * -thumbOffset + vec3i3.getZ() * palmOffset + vec3i.getZ() * fingerOffset ); } else { throw new IllegalArgumentException("Invalid forwards & up combination"); } } static class BlockCacheLoader extends CacheLoader { private final LevelReader level; private final boolean loadChunks; public BlockCacheLoader(LevelReader level, boolean loadChunks) { this.level = level; this.loadChunks = loadChunks; } public BlockInWorld load(BlockPos pos) { return new BlockInWorld(this.level, pos, this.loadChunks); } } public static class BlockPatternMatch { private final BlockPos frontTopLeft; private final Direction forwards; private final Direction up; private final LoadingCache cache; private final int width; private final int height; private final int depth; public BlockPatternMatch( BlockPos frontTopLeft, Direction forwards, Direction up, LoadingCache cache, int width, int height, int depth ) { this.frontTopLeft = frontTopLeft; this.forwards = forwards; this.up = up; this.cache = cache; this.width = width; this.height = height; this.depth = depth; } public BlockPos getFrontTopLeft() { return this.frontTopLeft; } public Direction getForwards() { return this.forwards; } public Direction getUp() { return this.up; } public int getWidth() { return this.width; } public int getHeight() { return this.height; } public int getDepth() { return this.depth; } public BlockInWorld getBlock(int palmOffset, int thumbOffset, int fingerOffset) { return this.cache.getUnchecked(BlockPattern.translateAndRotate(this.frontTopLeft, this.getForwards(), this.getUp(), palmOffset, thumbOffset, fingerOffset)); } public String toString() { return MoreObjects.toStringHelper(this).add("up", this.up).add("forwards", this.forwards).add("frontTopLeft", this.frontTopLeft).toString(); } } }