126 lines
5.1 KiB
Java
126 lines
5.1 KiB
Java
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<SchematicRegion> 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;
|
|
}
|
|
}
|