package com.simibubi.create.foundation.utility; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import com.google.common.base.Predicates; import net.minecraft.block.BlockState; import net.minecraft.block.LeavesBlock; import net.minecraft.tags.BlockTags; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockReader; public class TreeCutter { public static class Tree { public List logs; public List leaves; public Tree(List logs, List leaves) { this.logs = logs; this.leaves = leaves; } } /** * Finds a tree at the given pos. Block at the position should be air * * @param reader * @param pos * @return null if not found or not fully cut */ public static Tree cutTree(IBlockReader reader, BlockPos pos) { List logs = new ArrayList<>(); List leaves = new ArrayList<>(); Set visited = new HashSet<>(); List frontier = new LinkedList<>(); if (!validateCut(reader, pos)) return null; visited.add(pos); BlockPos.getAllInBox(pos.add(-1, 0, -1), pos.add(1, 1, 1)).forEach(p -> frontier.add(new BlockPos(p))); // Find all logs while (!frontier.isEmpty()) { BlockPos currentPos = frontier.remove(0); if (visited.contains(currentPos)) continue; visited.add(currentPos); if (!isLog(reader.getBlockState(currentPos))) continue; logs.add(currentPos); addNeighbours(currentPos, frontier, visited); } // Find all leaves visited.clear(); visited.addAll(logs); frontier.addAll(logs); while (!frontier.isEmpty()) { BlockPos currentPos = frontier.remove(0); if (!logs.contains(currentPos)) if (visited.contains(currentPos)) continue; visited.add(currentPos); BlockState blockState = reader.getBlockState(currentPos); boolean isLog = isLog(blockState); boolean isLeaf = isLeaf(blockState); if (!isLog && !isLeaf) continue; if (isLeaf) leaves.add(currentPos); int distance = isLog ? 0 : blockState.get(LeavesBlock.DISTANCE); for (Direction direction : Direction.values()) { BlockPos offset = currentPos.offset(direction); if (visited.contains(offset)) continue; BlockState state = reader.getBlockState(offset); if (isLeaf(state) && state.get(LeavesBlock.DISTANCE) > distance) frontier.add(offset); } } return new Tree(logs, leaves); } /** * Checks whether a tree was fully cut by seeing whether the layer above the cut * is not supported by any more logs. * * @param reader * @param pos * @return */ private static boolean validateCut(IBlockReader reader, BlockPos pos) { Set visited = new HashSet<>(); List frontier = new LinkedList<>(); frontier.add(pos); frontier.add(pos.up()); int posY = pos.getY(); while (!frontier.isEmpty()) { BlockPos currentPos = frontier.remove(0); visited.add(currentPos); boolean lowerLayer = currentPos.getY() == posY; if (!isLog(reader.getBlockState(currentPos))) continue; if (!lowerLayer && !pos.equals(currentPos.down()) && isLog(reader.getBlockState(currentPos.down()))) return false; for (Direction direction : Direction.values()) { if (direction == Direction.DOWN) continue; if (direction == Direction.UP && !lowerLayer) continue; BlockPos offset = currentPos.offset(direction); if (visited.contains(offset)) continue; frontier.add(offset); } } return true; } private static void addNeighbours(BlockPos pos, List frontier, Set visited) { BlockPos.getAllInBox(pos.add(-1, -1, -1), pos.add(1, 1, 1)).filter(Predicates.not(visited::contains)) .forEach(p -> frontier.add(new BlockPos(p))); } private static boolean isLog(BlockState state) { return state.isIn(BlockTags.LOGS); } private static boolean isLeaf(BlockState state) { return state.has(LeavesBlock.DISTANCE); } }