package de.lucas.litematica.placement; import de.lucas.litematica.schematic.LitematicSchematic; import de.lucas.litematica.schematic.SchematicRegion; import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.levelgen.structure.BoundingBox; import org.jetbrains.annotations.Nullable; import java.util.List; /** * Immutable snapshot of a placement: schematic + origin + transform, with block states already transformed per * palette entry. Render workers query blocks in world coordinates through this class without locking. * A new view is created whenever the placement changes. */ public final class PlacementView { private static final BlockState AIR = Blocks.AIR.defaultBlockState(); private final LitematicSchematic schematic; private final BlockPos origin; private final PlacementTransform transform; private final Vec3i localSize; private final BoundingBox box; private final List regions; private final BlockState[][] transformedPalettes; public PlacementView(LitematicSchematic schematic, BlockPos origin, PlacementTransform transform) { this.schematic = schematic; this.origin = origin; this.transform = transform; this.localSize = schematic.size(); Vec3i placed = transform.transformSize(this.localSize); this.box = new BoundingBox(origin.getX(), origin.getY(), origin.getZ(), origin.getX() + placed.getX() - 1, origin.getY() + placed.getY() - 1, origin.getZ() + placed.getZ() - 1); this.regions = schematic.regions(); this.transformedPalettes = new BlockState[this.regions.size()][]; for (int i = 0; i < this.regions.size(); i++) { BlockState[] palette = this.regions.get(i).palette(); BlockState[] transformed = new BlockState[palette.length]; for (int j = 0; j < palette.length; j++) { transformed[j] = transform.apply(palette[j]); } this.transformedPalettes[i] = transformed; } } public LitematicSchematic schematic() { return this.schematic; } public BlockPos origin() { return this.origin; } public PlacementTransform transform() { return this.transform; } /** World-space bounding box (inclusive). */ public BoundingBox box() { return this.box; } /** Transformed block state at a world position; air outside the schematic. {@code scratch} avoids allocation. */ public BlockState getState(int x, int y, int z, BlockPos.MutableBlockPos scratch) { int region = this.locate(x, y, z, scratch); if (region < 0) { return AIR; } SchematicRegion r = this.regions.get(region); return this.transformedPalettes[region][r.paletteIndex(scratch.getX(), scratch.getY(), scratch.getZ())]; } /** {@code true} if the schematic contains a block at this position that does not exist in the running game. */ public boolean isUnknown(int x, int y, int z, BlockPos.MutableBlockPos scratch) { int region = this.locate(x, y, z, scratch); if (region < 0) { return false; } SchematicRegion r = this.regions.get(region); return r.unknown()[r.paletteIndex(scratch.getX(), scratch.getY(), scratch.getZ())]; } @Nullable public CompoundTag getBlockEntity(int x, int y, int z, BlockPos.MutableBlockPos scratch) { int region = this.locate(x, y, z, scratch); return region < 0 ? null : this.regions.get(region).getBlockEntity(scratch.getX(), scratch.getY(), scratch.getZ()); } /** * Resolves a world position to a region index, leaving the region-local coordinates in {@code scratch}. * Returns -1 if no region covers the position. */ private int locate(int x, int y, int z, BlockPos.MutableBlockPos scratch) { if (!this.box.isInside(x, y, z)) { return -1; } this.transform.toLocal(x - this.origin.getX(), y - this.origin.getY(), z - this.origin.getZ(), this.localSize, scratch); int lx = scratch.getX(), ly = scratch.getY(), lz = scratch.getZ(); int fallback = -1; for (int i = 0; i < this.regions.size(); i++) { SchematicRegion r = this.regions.get(i); int rx = lx - r.min().getX(), ry = ly - r.min().getY(), rz = lz - r.min().getZ(); if (r.contains(rx, ry, rz)) { int index = r.paletteIndex(rx, ry, rz); if (this.regions.size() == 1 || !r.palette()[index].isAir() || r.unknown()[index]) { scratch.set(rx, ry, rz); return i; } if (fallback < 0) { fallback = i; } } } if (fallback >= 0) { SchematicRegion r = this.regions.get(fallback); scratch.set(lx - r.min().getX(), ly - r.min().getY(), lz - r.min().getZ()); } return fallback; } }