minecraft-src/net/minecraft/world/level/block/state/pattern/BlockPattern.java
2025-07-04 01:41:11 +03:00

195 lines
6.1 KiB
Java

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<BlockInWorld>[][][] pattern;
private final int depth;
private final int height;
private final int width;
public BlockPattern(Predicate<BlockInWorld>[][][] 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<BlockInWorld>[][][] getPattern() {
return this.pattern;
}
@Nullable
@VisibleForTesting
public BlockPattern.BlockPatternMatch matches(LevelReader level, BlockPos pos, Direction finger, Direction thumb) {
LoadingCache<BlockPos, BlockInWorld> 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<BlockPos, BlockInWorld> 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<BlockPos, BlockInWorld> 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<BlockPos, BlockInWorld> 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<BlockPos, BlockInWorld> {
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<BlockPos, BlockInWorld> cache;
private final int width;
private final int height;
private final int depth;
public BlockPatternMatch(
BlockPos frontTopLeft, Direction forwards, Direction up, LoadingCache<BlockPos, BlockInWorld> 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();
}
}
}