Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add settings to rotate/mirror schematics #4452

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/api/java/baritone/api/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -1089,6 +1092,28 @@ public final class Settings {
*/
public final Setting<Boolean> schematicOrientationZ = new Setting<>(false);

/**
* Rotates the schematic before building it.
* Possible values are
* <ul>
* <li> NONE - No rotation </li>
* <li> CLOCKWISE_90 - Rotate 90° clockwise </li>
* <li> CLOCKWISE_180 - Rotate 180° clockwise </li>
* <li> COUNTERCLOCKWISE_90 - Rotate 270° clockwise </li>
* </ul>
*/
public final Setting<Rotation> buildSchematicRotation = new Setting<>(Rotation.NONE);

/**
* Mirrors the schematic before building it.
* Possible values are
* <ul>
* <li> FRONT_BACK - mirror the schematic along its local x axis </li>
* <li> LEFT_RIGHT - mirror the schematic along its local z axis </li>
* </ul>
*/
public final Setting<Mirror> buildSchematicMirror = new Setting<>(Mirror.NONE);

/**
* The fallback used by the build command when no extension is specified. This may be useful if schematics of a
* particular format are used often, and the user does not wish to have to specify the extension with every usage.
Expand Down
114 changes: 114 additions & 0 deletions src/api/java/baritone/api/schematic/MirroredSchematic.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* This file is part of Baritone.
*
* Baritone is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Baritone is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
*/

package baritone.api.schematic;

import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.Mirror;

import java.util.List;
import java.util.stream.Collectors;

public class MirroredSchematic implements ISchematic {

private final ISchematic schematic;
private final Mirror mirror;

public MirroredSchematic(ISchematic schematic, Mirror mirror) {
this.schematic = schematic;
this.mirror = mirror;
}

@Override
public boolean inSchematic(int x, int y, int z, BlockState currentState) {
return schematic.inSchematic(
mirrorX(x, widthX(), mirror),
y,
mirrorZ(z, lengthZ(), mirror),
mirror(currentState, mirror)
);
}

@Override
public BlockState desiredState(int x, int y, int z, BlockState current, List<BlockState> approxPlaceable) {
return mirror(schematic.desiredState(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this flips the schematic in memory each time you call desiredState, I feel as though this could get really laggy on large schematics. Can we instead mirror it once in the constructor?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because the underlying schematic might have changed since the last call. We can only do this for static schematics, which most of our schematics are not (at least not reliably), and even then we might end up wasting memory by copying a sparse schematic into a dense array.
Also I think baking things in the wrapper schematic itself is actually a bad idea in most cases, because it forces the result to be static while we actually want it to be static iff it wraps a static schematic. Baking should be done separately from constructing the transformed schematic (i.e. schematic = new BakedSchematic(schematic)).

IIRC mirroring block states is fairly fast (cache lookup) so the potentially laggy part here is transforming up to 36 states from approxPlaceable. Given that we know how BuilderProcess uses desiredState we could commit a caching crime here (reuse last transformed if last untransformed == new untransformed, assuming the input wasn't mutated).

mirrorX(x, widthX(), mirror),
y,
mirrorZ(z, lengthZ(), mirror),
mirror(current, mirror),
mirror(approxPlaceable, mirror)
), mirror);
}

@Override
public void reset() {
schematic.reset();
}

@Override
public int widthX() {
return schematic.widthX();
}

@Override
public int heightY() {
return schematic.heightY();
}

@Override
public int lengthZ() {
return schematic.lengthZ();
}

private static int mirrorX(int x, int sizeX, Mirror mirror) {
switch (mirror) {
case NONE:
case LEFT_RIGHT:
return x;
case FRONT_BACK:
return sizeX - x - 1;
}
throw new IllegalArgumentException("Unknown mirror");
}

private static int mirrorZ(int z, int sizeZ, Mirror mirror) {
switch (mirror) {
case NONE:
case FRONT_BACK:
return z;
case LEFT_RIGHT:
return sizeZ - z - 1;
}
throw new IllegalArgumentException("Unknown mirror");
}

private static BlockState mirror(BlockState state, Mirror mirror) {
if (state == null) {
return null;
}
return state.mirror(mirror);
}

private static List<BlockState> mirror(List<BlockState> states, Mirror mirror) {
if (states == null) {
return null;
}
return states.stream()
.map(s -> mirror(s, mirror))
.collect(Collectors.toList());
}
}
136 changes: 136 additions & 0 deletions src/api/java/baritone/api/schematic/RotatedSchematic.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* This file is part of Baritone.
*
* Baritone is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Baritone is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
*/

package baritone.api.schematic;

import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.Rotation;

import java.util.List;
import java.util.stream.Collectors;

public class RotatedSchematic implements ISchematic {

private final ISchematic schematic;
private final Rotation rotation;
private final Rotation inverseRotation;

public RotatedSchematic(ISchematic schematic, Rotation rotation) {
this.schematic = schematic;
this.rotation = rotation;
// I don't think a 14 line switch would improve readability
this.inverseRotation = rotation.getRotated(rotation).getRotated(rotation);
}

@Override
public boolean inSchematic(int x, int y, int z, BlockState currentState) {
return schematic.inSchematic(
rotateX(x, z, widthX(), lengthZ(), inverseRotation),
y,
rotateZ(x, z, widthX(), lengthZ(), inverseRotation),
rotate(currentState, inverseRotation)
);
}

@Override
public BlockState desiredState(int x, int y, int z, BlockState current, List<BlockState> approxPlaceable) {
return rotate(schematic.desiredState(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

rotateX(x, z, widthX(), lengthZ(), inverseRotation),
y,
rotateZ(x, z, widthX(), lengthZ(), inverseRotation),
rotate(current, inverseRotation),
rotate(approxPlaceable, inverseRotation)
), rotation);
}

@Override
public void reset() {
schematic.reset();
}

@Override
public int widthX() {
return flipsCoordinates(rotation) ? schematic.lengthZ() : schematic.widthX();
}

@Override
public int heightY() {
return schematic.heightY();
}

@Override
public int lengthZ() {
return flipsCoordinates(rotation) ? schematic.widthX() : schematic.lengthZ();
}

/**
* Wether {@code rotation} swaps the x and z components
*/
private static boolean flipsCoordinates(Rotation rotation) {
return rotation == Rotation.CLOCKWISE_90 || rotation == Rotation.COUNTERCLOCKWISE_90;
}

/**
* The x component of x,y after applying the rotation
*/
private static int rotateX(int x, int z, int sizeX, int sizeZ, Rotation rotation) {
switch (rotation) {
case NONE:
return x;
case CLOCKWISE_90:
return sizeZ - z - 1;
case CLOCKWISE_180:
return sizeX - x - 1;
case COUNTERCLOCKWISE_90:
return z;
}
throw new IllegalArgumentException("Unknown rotation");
}

/**
* The z component of x,y after applying the rotation
*/
private static int rotateZ(int x, int z, int sizeX, int sizeZ, Rotation rotation) {
switch (rotation) {
case NONE:
return z;
case CLOCKWISE_90:
return x;
case CLOCKWISE_180:
return sizeZ - z - 1;
case COUNTERCLOCKWISE_90:
return sizeX - x - 1;
}
throw new IllegalArgumentException("Unknown rotation");
}

private static BlockState rotate(BlockState state, Rotation rotation) {
if (state == null) {
return null;
}
return state.rotate(rotation);
}

private static List<BlockState> rotate(List<BlockState> states, Rotation rotation) {
if (states == null) {
return null;
}
return states.stream()
.map(s -> rotate(s, rotation))
.collect(Collectors.toList());
}
}
6 changes: 5 additions & 1 deletion src/api/java/baritone/api/utils/SettingsUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;

import java.awt.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
Expand Down Expand Up @@ -220,7 +223,8 @@ private enum Parser implements ISettingParser {
FLOAT(Float.class, Float::parseFloat),
LONG(Long.class, Long::parseLong),
STRING(String.class, String::new),
DIRECTION(Direction.class, Direction::byName),
MIRROR(Mirror.class, Mirror::valueOf, Mirror::name),
ROTATION(Rotation.class, Rotation::valueOf, Rotation::name),
COLOR(
Color.class,
str -> new Color(Integer.parseInt(str.split(",")[0]), Integer.parseInt(str.split(",")[1]), Integer.parseInt(str.split(",")[2])),
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/baritone/process/BuilderProcess.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import baritone.api.schematic.IStaticSchematic;
import baritone.api.schematic.MaskSchematic;
import baritone.api.schematic.SubstituteSchematic;
import baritone.api.schematic.RotatedSchematic;
import baritone.api.schematic.MirroredSchematic;
import baritone.api.schematic.format.ISchematicFormat;
import baritone.api.utils.*;
import baritone.api.utils.input.Input;
Expand Down Expand Up @@ -119,6 +121,12 @@ public void build(String name, ISchematic schematic, Vec3i origin) {
if (!Baritone.settings().buildSubstitutes.value.isEmpty()) {
this.schematic = new SubstituteSchematic(this.schematic, Baritone.settings().buildSubstitutes.value);
}
if (Baritone.settings().buildSchematicMirror.value != net.minecraft.world.level.block.Mirror.NONE) {
this.schematic = new MirroredSchematic(this.schematic, Baritone.settings().buildSchematicMirror.value);
}
if (Baritone.settings().buildSchematicRotation.value != net.minecraft.world.level.block.Rotation.NONE) {
this.schematic = new RotatedSchematic(this.schematic, Baritone.settings().buildSchematicRotation.value);
}
// TODO this preserves the old behavior, but maybe we should bake the setting value right here
this.schematic = new MaskSchematic(this.schematic) {
@Override
Expand Down