From 31ac2b69d1e650bae1063dbd272af9de0b72b7b3 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Sun, 7 Apr 2019 17:41:26 +1000 Subject: [PATCH] Fix block rotation --- .../fawe/object/schematic/Schematic.java | 5 +- .../command/FlattenedClipboardTransform.java | 4 +- .../transform/BlockTransformExtent.java | 615 +++++++++++------- .../transform/BlockTransformExtent2.java | 322 --------- .../function/visitor/RegionVisitor.java | 1 + .../sk89q/worldedit/session/PasteBuilder.java | 3 +- .../com/sk89q/worldedit/util/Direction.java | 11 +- .../worldedit/world/block/BaseBlock.java | 2 +- .../world/block/BlockStateHolder.java | 49 +- 9 files changed, 460 insertions(+), 552 deletions(-) delete mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent2.java diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/schematic/Schematic.java b/worldedit-core/src/main/java/com/boydti/fawe/object/schematic/Schematic.java index 840a0780b..0b2ef0e2d 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/schematic/Schematic.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/schematic/Schematic.java @@ -15,7 +15,6 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; import com.sk89q.worldedit.extent.transform.BlockTransformExtent; -import com.sk89q.worldedit.extent.transform.BlockTransformExtent2; import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.mask.ExistingBlockMask; import com.sk89q.worldedit.function.mask.Mask; @@ -138,7 +137,7 @@ public class Schematic { Extent extent = clipboard; Mask sourceMask = editSession.getSourceMask(); if (transform != null && !transform.isIdentity()) { - extent = new BlockTransformExtent2(clipboard, transform); + extent = new BlockTransformExtent(clipboard, transform); } else if (sourceMask == null) { paste(editSession, to, pasteAir); editSession.flushQueue(); @@ -171,7 +170,7 @@ public class Schematic { Region region = clipboard.getRegion(); Extent source = clipboard; if (transform != null) { - source = new BlockTransformExtent2(clipboard, transform); + source = new BlockTransformExtent(clipboard, transform); } ForwardExtentCopy copy = new ForwardExtentCopy(source, clipboard.getRegion(), clipboard.getOrigin(), extent, to); if (transform != null) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java index 997bb8711..5e2bdd9b8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java @@ -24,10 +24,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.transform.BlockTransformExtent; -import com.sk89q.worldedit.extent.transform.BlockTransformExtent2; import com.sk89q.worldedit.function.operation.ForwardExtentCopy; import com.sk89q.worldedit.function.operation.Operation; -import com.sk89q.worldedit.math.MutableVector3; import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.math.transform.CombinedTransform; @@ -118,7 +116,7 @@ public class FlattenedClipboardTransform { */ public Operation copyTo(Extent target) { Extent extent = original; - if (transform != null && !transform.isIdentity()) extent = new BlockTransformExtent2(original, transform); + if (transform != null && !transform.isIdentity()) extent = new BlockTransformExtent(original, transform); ForwardExtentCopy copy = new ForwardExtentCopy(extent, original.getRegion(), original.getOrigin(), target, original.getOrigin()); copy.setTransform(transform); return copy; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java index a8cb69008..facd76745 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java @@ -1,276 +1,455 @@ -/* - * WorldEdit, a Minecraft world manipulation toolkit - * Copyright (C) sk89q - * Copyright (C) WorldEdit team and contributors - * - * This program 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. - * - * This program 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 this program. If not, see . - */ - package com.sk89q.worldedit.extent.transform; -import static com.google.common.base.Preconditions.checkNotNull; - import com.boydti.fawe.object.extent.ResettableExtent; import com.boydti.fawe.util.ReflectionUtils; -import com.google.common.collect.Sets; import com.sk89q.jnbt.ByteTag; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.extent.AbstractDelegateExtent; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.helper.MCDirections; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.math.transform.Transform; -import com.sk89q.worldedit.registry.state.BooleanProperty; +import com.sk89q.worldedit.registry.state.AbstractProperty; import com.sk89q.worldedit.registry.state.DirectionalProperty; -import com.sk89q.worldedit.registry.state.EnumProperty; -import com.sk89q.worldedit.registry.state.IntegerProperty; import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.registry.state.PropertyKey; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; - -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.OptionalInt; -import java.util.Set; -import java.util.stream.Collectors; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static com.sk89q.worldedit.util.Direction.*; -/** - * Transforms blocks themselves (but not their position) according to a - * given transform. - */ public class BlockTransformExtent extends ResettableExtent { - private Transform transform; + private Transform transformInverse; + private int[] BLOCK_ROTATION_BITMASK; + private int[][] BLOCK_TRANSFORM; + private int[][] BLOCK_TRANSFORM_INVERSE; + private int[] ALL = new int[0]; - public BlockTransformExtent(Extent parent) { this(parent, new AffineTransform()); } - /** - * Create a new instance. - * - * @param extent the extent - */ - public BlockTransformExtent(Extent extent, Transform transform) { - super(extent); - checkNotNull(transform); + public BlockTransformExtent(Extent parent, Transform transform) { + super(parent); this.transform = transform; + this.transformInverse = this.transform.inverse(); + cache(); + } + + + private long combine(Direction... directions) { + int mask = 0; + for (Direction dir : directions) { + mask = mask | (1 << dir.ordinal()); + } + return mask; + } + + private long[] adapt(Direction... dirs) { + long[] arr = new long[dirs.length]; + for (int i = 0; i < arr.length; i++) { + arr[i] = 1 << dirs[i].ordinal(); + } + return arr; + } + + private long[] adapt(Long... dirs) { + long[] arr = new long[dirs.length]; + for (int i = 0; i < arr.length; i++) { + arr[i] = dirs[i]; + } + return arr; + } + + private long[] getDirections(AbstractProperty property) { + if (property instanceof DirectionalProperty) { + DirectionalProperty directional = (DirectionalProperty) property; + return adapt(directional.getValues().toArray(new Direction[0])); + } else { + List values = property.getValues(); + switch (property.getKey()) { + case HALF: + return adapt(UP, DOWN); + case ROTATION: { + List directions = new ArrayList<>(); + for (Object value : values) { + directions.add(Direction.fromRotationIndex((Integer) value).get()); + } + return adapt(directions.toArray(new Direction[0])); + } + case AXIS: + switch (property.getValues().size()) { + case 3: + return adapt(EAST, UP, SOUTH); + case 2: + return adapt(combine(EAST, WEST), combine(SOUTH, NORTH)); + default: + System.out.println("Invalid " + property.getName() + " " + property.getValues()); + return null; + } + case FACING: { + List directions = new ArrayList<>(); + for (Object value : values) { + directions.add(Direction.valueOf(value.toString().toUpperCase())); + } + return adapt(directions.toArray(new Direction[0])); + } + case SHAPE: + if (values.contains("straight")) { + ArrayList result = new ArrayList<>(); + for (Object value : values) { + // [straight, inner_left, inner_right, outer_left, outer_right] + switch (value.toString()) { + case "straight": + result.add(combine(NORTH, EAST, SOUTH, WEST)); + continue; + case "inner_left": + result.add(notIndex(combine(NORTHEAST, SOUTHWEST), property.getIndexFor("outer_right"))); + continue; + case "inner_right": + result.add(notIndex(combine(NORTHWEST, SOUTHEAST), property.getIndexFor("outer_left"))); + continue; + case "outer_left": + result.add(notIndex(combine(NORTHEAST, SOUTHWEST), property.getIndexFor("inner_right"))); + continue; + case "outer_right": + result.add(notIndex(combine(NORTHWEST, SOUTHEAST), property.getIndexFor("inner_left"))); + continue; + default: + System.out.println("Unknown direction " + value); + result.add(0l); + } + } + return adapt(result.toArray(new Long[0])); + } else { + List directions = new ArrayList<>(); + for (Object value : values) { + switch (value.toString()) { + case "north_south": + directions.add(combine(NORTH, SOUTH)); + break; + case "east_west": + directions.add(combine(EAST, WEST)); + break; + case "ascending_east": + directions.add(combine(ASCENDING_EAST)); + break; + case "ascending_west": + directions.add(combine(ASCENDING_WEST)); + break; + case "ascending_north": + directions.add(combine(ASCENDING_NORTH)); + break; + case "ascending_south": + directions.add(combine(ASCENDING_SOUTH)); + break; + case "south_east": + directions.add(combine(SOUTHEAST)); + break; + case "south_west": + directions.add(combine(SOUTHWEST)); + break; + case "north_west": + directions.add(combine(NORTHWEST)); + break; + case "north_east": + directions.add(combine(NORTHEAST)); + break; + default: + System.out.println("Unknown direction " + value); + directions.add(0l); + } + } + return adapt(directions.toArray(new Long[0])); + } + } + } + return null; + } + + private static Direction getFirst(long mask) { + for (Direction dir : Direction.values()) { + if (hasDirection(mask, dir)) { + return dir; + } + } + return null; + } + + private static boolean hasDirection(long mask, Direction dir) { + return (mask & (1L << dir.ordinal())) != 0; + } + + private static long notIndex(long mask, int index) { + return mask | (1L << (index + values().length)); + } + + private static boolean hasIndex(long mask, int index) { + return ((mask >> values().length) & (1 << index)) == 0; + } + + @Nullable + private static Integer getNewStateIndex(Transform transform, long[] directions, int oldIndex) { + long oldDirMask = directions[oldIndex]; + if (oldDirMask == 0) { + return oldIndex; + } + for (Direction oldDirection : values()) { + if (!hasDirection(oldDirMask, oldDirection)) continue; + if (oldDirection == null) { + System.out.println(oldDirMask); + } + Vector3 oldVector = oldDirection.toVector(); + Vector3 newVector = transform.apply(oldVector).subtract(transform.apply(Vector3.ZERO)).normalize(); + int newIndex = oldIndex; + double closest = oldVector.normalize().dot(newVector); + boolean found = false; + for (int i = 0; i < directions.length; i++) { + int j = (oldIndex + i) % directions.length; + long newDirMask = directions[j]; + if (!hasIndex(oldDirMask, j)) continue; + for (Direction v : Direction.values()) { + // Check if it's one of the current directions + if (!hasDirection(newDirMask, v)) continue; + // Check if the old mask excludes it + double dot = v.toVector().normalize().dot(newVector); + if (dot > closest) { + closest = dot; + newIndex = j; + found = true; + } + } + } + if (found) { + return newIndex; + } + } + return null; + } + + private boolean isDirectional(Property property) { + if (property instanceof DirectionalProperty) { + return true; + } + switch (property.getKey()) { + case HALF: + case ROTATION: + case AXIS: + case FACING: + case SHAPE: + case NORTH: + case EAST: + case SOUTH: + case WEST: + return true; + default: + return false; + } + } + + private void cache() { + BLOCK_ROTATION_BITMASK = new int[BlockTypes.size()]; + BLOCK_TRANSFORM = new int[BlockTypes.size()][]; + BLOCK_TRANSFORM_INVERSE = new int[BlockTypes.size()][]; + outer: + for (int i = 0; i < BLOCK_TRANSFORM.length; i++) { + BLOCK_TRANSFORM[i] = ALL; + BLOCK_TRANSFORM_INVERSE[i] = ALL; + BlockType type = BlockTypes.get(i); + int bitMask = 0; + for (AbstractProperty property : (Collection) (Collection) type.getProperties()) { + if (isDirectional(property)) { + BLOCK_TRANSFORM[i] = null; + BLOCK_TRANSFORM_INVERSE[i] = null; + bitMask |= property.getBitMask(); + } + } + if (bitMask != 0) { + BLOCK_ROTATION_BITMASK[i] = bitMask; + } + } + } + + @Override + public ResettableExtent setExtent(Extent extent) { + return super.setExtent(extent); } - /** - * Get the transform. - * - * @return the transform - */ public Transform getTransform() { return transform; } - - /** - * Set the transform - * @param affine - */ + public void setTransform(Transform affine) { this.transform = affine; + this.transformInverse = this.transform.inverse(); + cache(); } + private final BlockState transform(BlockState state, int[][] transformArray, Transform transform) { + try { + int typeId = state.getInternalBlockTypeId(); + int[] arr = transformArray[typeId]; + if (arr == ALL) { + return state; + } + if (arr == null) { + arr = transformArray[typeId] = new int[state.getBlockType().getMaxStateId() + 1]; + Arrays.fill(arr, -1); + } + int mask = BLOCK_ROTATION_BITMASK[typeId]; + int internalId = state.getInternalId(); - /** - * Transform a block without making a copy. - * - * @param block the block - * @param reverse true to transform in the opposite direction - * @return the same block - */ - protected > T transformBlock(T block, boolean reverse) { - return transform(block, reverse ? transform.inverse() : transform); + int maskedId = internalId & mask; + int newMaskedId = arr[maskedId >> BlockTypes.BIT_OFFSET]; + if (newMaskedId != -1) { + return BlockState.getFromInternalId(newMaskedId | (internalId & (~mask))); + } + newMaskedId = state.getInternalId(); + + BlockType type = state.getBlockType(); + + // Rotate North, East, South, West + if (type.hasProperty(PropertyKey.NORTH) && type.hasProperty(PropertyKey.EAST) && type.hasProperty(PropertyKey.SOUTH) && type.hasProperty(PropertyKey.WEST)) { + Direction newNorth = findClosest(transform.apply(NORTH.toVector()), Flag.CARDINAL); + Direction newEast = findClosest(transform.apply(EAST.toVector()), Flag.CARDINAL); + Direction newSouth = findClosest(transform.apply(SOUTH.toVector()), Flag.CARDINAL); + Direction newWest = findClosest(transform.apply(WEST.toVector()), Flag.CARDINAL); + + BlockState tmp = state; + + Object northState = tmp.getState(PropertyKey.NORTH); + Object eastState = tmp.getState(PropertyKey.EAST); + Object southState = tmp.getState(PropertyKey.SOUTH); + Object westState = tmp.getState(PropertyKey.WEST); + + tmp = tmp.with(PropertyKey.valueOf(newNorth.name().toUpperCase()), northState); + tmp = tmp.with(PropertyKey.valueOf(newEast.name().toUpperCase()), eastState); + tmp = tmp.with(PropertyKey.valueOf(newSouth.name().toUpperCase()), southState); + tmp = tmp.with(PropertyKey.valueOf(newWest.name().toUpperCase()), westState); + + newMaskedId = tmp.getInternalId(); + } + + for (AbstractProperty property : (Collection) (Collection) type.getProperties()) { + long[] directions = getDirections(property); + if (directions != null) { + Integer newIndex = getNewStateIndex(transform, directions, property.getIndex(state.getInternalId())); + if (newIndex != null) { + newMaskedId = property.modifyIndex(newMaskedId, newIndex); + } + } + } + arr[maskedId >> BlockTypes.BIT_OFFSET] = newMaskedId & mask; + return BlockState.getFromInternalId(newMaskedId); + } catch (Throwable e) { + e.printStackTrace(); + throw e; + } } - - @Override - public BlockState getLazyBlock(BlockVector3 position) { - return transformBlock(super.getLazyBlock(position), false).toImmutableState(); + + public final BaseBlock transformInverse(BlockStateHolder block) { + BlockState transformed = transform(block.toImmutableState()); + if (block.hasNbtData()) { + return transformFastWith(transformed, block.getNbtData(), transformInverse); + } + return transformed.toBaseBlock(); } - + + public final BlockStateHolder transform(BlockStateHolder block) { + BlockState transformed = transform(block.toImmutableState()); + if (block.hasNbtData()) { + return transformFastWith(transformed, block.getNbtData(), transform); + } + return transformed; + } + + public final BaseBlock transformFastWith(BlockState transformed, CompoundTag tag, Transform transform) { + if (tag != null) { + if (tag.containsKey("Rot")) { + int rot = tag.asInt("Rot"); + + Direction direction = MCDirections.fromRotation(rot); + + if (direction != null) { + Vector3 applyAbsolute = transform.apply(direction.toVector()); + Vector3 applyOrigin = transform.apply(Vector3.ZERO); + applyAbsolute.mutX(applyAbsolute.getX() - applyOrigin.getX()); + applyAbsolute.mutY(applyAbsolute.getY() - applyOrigin.getY()); + applyAbsolute.mutZ(applyAbsolute.getZ() - applyOrigin.getZ()); + + Direction newDirection = Direction.findClosest(applyAbsolute, Direction.Flag.CARDINAL | Direction.Flag.ORDINAL | Direction.Flag.SECONDARY_ORDINAL); + + if (newDirection != null) { + Map values = ReflectionUtils.getMap(tag.getValue()); + values.put("Rot", new ByteTag((byte) MCDirections.toRotation(newDirection))); + } + } + return new BaseBlock(transformed, tag); + } + } + return transformed.toBaseBlock(); + } + + public final BlockState transformInverse(BlockState block) { + return transform(block, BLOCK_TRANSFORM, transformInverse); + } + + public final BlockState transform(BlockState block) { + return transform(block, BLOCK_TRANSFORM_INVERSE, transform); + } + @Override public BlockState getLazyBlock(int x, int y, int z) { - return transformBlock(super.getLazyBlock(x, y, z), false).toImmutableState(); - } - - @Override - public BlockState getBlock(BlockVector3 position) { - return transformBlock(super.getBlock(position), false); + return transformInverse(super.getLazyBlock(x, y, z)); } @Override public BaseBlock getFullBlock(BlockVector3 position) { - return transformBlock(super.getFullBlock(position), false); - } - - @Override - public > boolean setBlock(int x, int y, int z, B block) throws WorldEditException { - return super.setBlock(x, y, z, transformBlock(block, true)); + return transformInverse(super.getFullBlock(position)); } @Override - public > boolean setBlock(BlockVector3 location, B block) throws WorldEditException { - return super.setBlock(location, transformBlock(block, true)); - } - - private static final Set directionNames = Sets.newHashSet("north", "south", "east", "west"); - - /** - * Transform the given block using the given transform. - * - *

The provided block is not modified.

- * - * @param block the block - * @param transform the transform - * @return the same block - */ - public static > B transform(B block, Transform transform) { - checkNotNull(block); - checkNotNull(transform); - - B result = block; - List> properties = block.getBlockType().getProperties(); - - for (Property property : properties) { - if (property instanceof DirectionalProperty) { - DirectionalProperty dirProp = (DirectionalProperty) property; - Direction value = (Direction) block.getState(property); - if (value != null) { - Vector3 newValue = getNewStateValue(dirProp.getValues(), transform, value.toVector()); - if (newValue != null) { - result = result.with(dirProp, Direction.findClosest(newValue, Direction.Flag.ALL)); - } - } - } else if (property instanceof EnumProperty) { - EnumProperty enumProp = (EnumProperty) property; - if (property.getName().equals("axis")) { - // We have an axis - this is something we can do the rotations to :sunglasses: - Direction value = null; - switch ((String) block.getState(property)) { - case "x": - value = Direction.EAST; - break; - case "y": - value = Direction.UP; - break; - case "z": - value = Direction.NORTH; - break; - } - if (value != null) { - Vector3 newValue = getNewStateValue(Direction.valuesOf(Direction.Flag.UPRIGHT | Direction.Flag.CARDINAL), transform, value.toVector()); - if (newValue != null) { - String axis = null; - Direction newDir = Direction.findClosest(newValue, Direction.Flag.UPRIGHT | Direction.Flag.CARDINAL); - if (newDir == Direction.NORTH || newDir == Direction.SOUTH) { - axis = "z"; - } else if (newDir == Direction.EAST || newDir == Direction.WEST) { - axis = "x"; - } else if (newDir == Direction.UP || newDir == Direction.DOWN) { - axis = "y"; - } - if (axis != null) { - result = result.with(enumProp, axis); - } - } - } - } - } else if (property instanceof IntegerProperty) { - IntegerProperty intProp = (IntegerProperty) property; - if (property.getName().equals("rotation")) { - if (intProp.getValues().size() == 16) { - Optional direction = Direction.fromRotationIndex(block.getState(intProp)); - int horizontalFlags = Direction.Flag.CARDINAL | Direction.Flag.ORDINAL | Direction.Flag.SECONDARY_ORDINAL; - if (direction.isPresent()) { - Vector3 vec = getNewStateValue(Direction.valuesOf(horizontalFlags), transform, direction.get().toVector()); - if (vec != null) { - OptionalInt newRotation = Direction.findClosest(vec, horizontalFlags).toRotationIndex(); - if (newRotation.isPresent()) { - result = result.with(intProp, newRotation.getAsInt()); - } - } - } - } - } - } - } - - List directionalProperties = properties.stream() - .filter(prop -> prop instanceof BooleanProperty) - .filter(prop -> directionNames.contains(prop.getName())) - .filter(property -> (Boolean) block.getState(property)) - .map(Property::getName) - .map(String::toUpperCase) - .map(Direction::valueOf) - .map(dir -> Direction.findClosest(transform.apply(dir.toVector()), Direction.Flag.CARDINAL)) - .filter(Objects::nonNull) - .map(Direction::name) - .map(String::toLowerCase) - .collect(Collectors.toList()); - - if (directionalProperties.size() > 0) { - for (String directionName : directionNames) { - result = result.with(block.getBlockType().getProperty(directionName), directionalProperties.contains(directionName)); - } - } - - return result; + public BlockState getLazyBlock(BlockVector3 position) { + return transformInverse(super.getLazyBlock(position)); } - /** - * Get the new value with the transformed direction. - * - * @param allowedStates the allowed states - * @param transform the transform - * @param oldDirection the old direction to transform - * @return a new state or null if none could be found - */ - @Nullable - private static Vector3 getNewStateValue(List allowedStates, Transform transform, Vector3 oldDirection) { - Vector3 newDirection = transform.apply(oldDirection).subtract(transform.apply(Vector3.ZERO)).normalize(); - Vector3 newValue = null; - double closest = -2; - boolean found = false; - - for (Direction v : allowedStates) { - double dot = v.toVector().normalize().dot(newDirection); - if (dot >= closest) { - closest = dot; - newValue = v.toVector(); - found = true; - } - } - - if (found) { - return newValue; - } else { - return null; - } + @Override + public BlockState getBlock(BlockVector3 position) { + return transformInverse(super.getBlock(position)); } + @Override + public BiomeType getBiome(BlockVector2 position) { + return super.getBiome(position); + } + + @Override + public boolean setBlock(int x, int y, int z, BlockStateHolder block) throws WorldEditException { + return super.setBlock(x, y, z, transform(block)); + } + + + @Override + public boolean setBlock(BlockVector3 location, BlockStateHolder block) throws WorldEditException { + return super.setBlock(location, transform(block)); + } + + } \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent2.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent2.java deleted file mode 100644 index 5a7e7cc17..000000000 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent2.java +++ /dev/null @@ -1,322 +0,0 @@ -package com.sk89q.worldedit.extent.transform; - -import com.boydti.fawe.object.extent.ResettableExtent; -import com.boydti.fawe.util.ReflectionUtils; -import com.boydti.fawe.util.StringMan; -import com.sk89q.jnbt.ByteTag; -import com.sk89q.jnbt.CompoundTag; -import com.sk89q.jnbt.Tag; -import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.internal.helper.MCDirections; -import com.sk89q.worldedit.math.BlockVector2; -import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.math.Vector3; -import com.sk89q.worldedit.math.transform.AffineTransform; -import com.sk89q.worldedit.math.transform.Transform; -import com.sk89q.worldedit.registry.state.AbstractProperty; -import com.sk89q.worldedit.registry.state.DirectionalProperty; -import com.sk89q.worldedit.registry.state.Property; -import com.sk89q.worldedit.util.Direction; -import com.sk89q.worldedit.world.biome.BiomeType; -import com.sk89q.worldedit.world.block.BaseBlock; -import com.sk89q.worldedit.world.block.BlockState; -import com.sk89q.worldedit.world.block.BlockStateHolder; -import com.sk89q.worldedit.world.block.BlockType; -import com.sk89q.worldedit.world.block.BlockTypes; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Vector; - -public class BlockTransformExtent2 extends ResettableExtent { - private Transform transform; - private Transform transformInverse; - private int[] BLOCK_ROTATION_BITMASK; - private int[][] BLOCK_TRANSFORM; - private int[][] BLOCK_TRANSFORM_INVERSE; - private int[] ALL = new int[0]; - - public BlockTransformExtent2(Extent parent) { - this(parent, new AffineTransform()); - } - - public BlockTransformExtent2(Extent parent, Transform transform) { - super(parent); - this.transform = transform; - this.transformInverse = this.transform.inverse(); - cache(); - } - - - private int combine(Direction... directions) { - int mask = 0; - for (Direction dir : directions) { - mask = mask & (1 << dir.ordinal()); - } - return mask; - } - - private int[] adapt(Direction... dirs) { - int[] arr = new int[dirs.length]; - for (int i = 0; i < arr.length; i++) { - arr[i] = 1 << dirs[i].ordinal(); - } - return arr; - } - - private int[] adapt(int... dirs) { - int[] arr = new int[dirs.length]; - for (int i = 0; i < arr.length; i++) { - arr[i] = dirs[i]; - } - return arr; - } - - private int[] getDirections(AbstractProperty property) { - if (property instanceof DirectionalProperty) { - DirectionalProperty directional = (DirectionalProperty) property; - directional.getValues(); - } else { - List values = property.getValues(); - switch (property.getKey()) { - case HALF: - return adapt(Direction.UP, Direction.DOWN); - case ROTATION: { - List directions = new ArrayList<>(); - for (Object value : values) { - directions.add(Direction.fromRotationIndex((Integer) value).get()); - } - return adapt(directions.toArray(new Direction[0])); - } - case AXIS: - switch (property.getValues().size()) { - case 3: - return adapt(Direction.EAST, Direction.UP, Direction.SOUTH); - case 2: - return adapt(combine(Direction.EAST, Direction.WEST), combine(Direction.SOUTH, Direction.NORTH)); - default: - System.out.println("Invalid " + property.getName() + " " + property.getValues()); - return null; - } - case FACING: { - List directions = new ArrayList<>(); - for (Object value : values) { - directions.add(Direction.valueOf(value.toString().toUpperCase())); - } - return adapt(directions.toArray(new Direction[0])); - } - case SHAPE: - - - case NORTH: - case EAST: - case SOUTH: - case WEST: - } - } - return null; - } - - @Nullable - private static Integer getNewStateIndex(Transform transform, List directions, int oldIndex) { - Direction oldDirection = directions.get(oldIndex); - Vector3 oldVector = oldDirection.toVector(); - Vector3 newVector = transform.apply(oldVector).subtract(transform.apply(Vector3.ZERO)).normalize(); - int newIndex = oldIndex; - double closest = oldVector.normalize().dot(newVector); - boolean found = false; - for (int i = 0; i < directions.size(); i++) { - Direction v = directions.get(i); - double dot = v.toVector().normalize().dot(newVector); - if (dot > closest) { - closest = dot; - newIndex = i; - found = true; - } - } - - if (found) { - return newIndex; - } else { - return null; - } - } - - private boolean isDirectional(Property property) { - if (property instanceof DirectionalProperty) { - return true; - } - switch (property.getKey()) { - case HALF: - case ROTATION: - case AXIS: - case FACING: - case SHAPE: - case NORTH: - case EAST: - case SOUTH: - case WEST: - return true; - default: - return false; - } - } - - private void cache() { - BLOCK_ROTATION_BITMASK = new int[BlockTypes.size()]; - BLOCK_TRANSFORM = new int[BlockTypes.size()][]; - BLOCK_TRANSFORM_INVERSE = new int[BlockTypes.size()][]; - outer: - for (int i = 0; i < BLOCK_TRANSFORM.length; i++) { - BLOCK_TRANSFORM[i] = ALL; - BLOCK_TRANSFORM_INVERSE[i] = ALL; - BlockType type = BlockTypes.get(i); - int bitMask = 0; - for (AbstractProperty property : (Collection) (Collection) type.getProperties()) { - if (isDirectional(property)) { - BLOCK_TRANSFORM[i] = null; - BLOCK_TRANSFORM_INVERSE[i] = null; - bitMask |= property.getBitMask(); - } - } - if (bitMask != 0) { - BLOCK_ROTATION_BITMASK[i] = bitMask; - } - } - } - - @Override - public ResettableExtent setExtent(Extent extent) { - return super.setExtent(extent); - } - - public Transform getTransform() { - return transform; - } - - public void setTransform(Transform affine) { - this.transform = affine; - this.transformInverse = this.transform.inverse(); - cache(); - } - - private final BlockState transform(BlockState state, int[][] transformArray, Transform transform) { - int typeId = state.getInternalBlockTypeId(); - int[] arr = transformArray[typeId]; - if (arr == ALL) return state; - if (arr == null) { - arr = transformArray[typeId] = new int[state.getBlockType().getMaxStateId() + 1]; - Arrays.fill(arr, -1); - } - int mask = BLOCK_ROTATION_BITMASK[typeId]; - int internalId = state.getInternalId(); - - int maskedId = internalId & mask; - int newMaskedId = arr[maskedId]; - if (newMaskedId != -1) { - return BlockState.getFromInternalId(newMaskedId | (internalId & (~mask))); - } - newMaskedId = state.getInternalId(); - - BlockType type = state.getBlockType(); - for (AbstractProperty property : (Collection) (Collection) type.getProperties()) { - if (isDirectional(property)) { - List directions = getDirections(property); - if (directions != null) { - Integer newIndex = getNewStateIndex(transform, directions, property.getIndex(state.getInternalId())); - if (newIndex != null) { - newMaskedId = property.modifyIndex(newMaskedId, newIndex); - } - } - } - } - arr[maskedId] = newMaskedId & mask; - return BlockState.getFromInternalId(newMaskedId); - } - - public final BaseBlock transformFast(BaseBlock block) { - BlockState transformed = transformFast(block.toImmutableState()); - return transformFastWith(transformed, block.getNbtData(), transform); - } - - public final BaseBlock transformInverse(BaseBlock block) { - BlockState transformed = transformFastInverse(block.toImmutableState()); - return transformFastWith(transformed, block.getNbtData(), transformInverse); - } - - public final BaseBlock transformFastWith(BlockState transformed, CompoundTag tag, Transform transform) { - if (tag != null) { - if (tag.containsKey("Rot")) { - int rot = tag.asInt("Rot"); - - Direction direction = MCDirections.fromRotation(rot); - - if (direction != null) { - Vector3 applyAbsolute = transform.apply(direction.toVector()); - Vector3 applyOrigin = transform.apply(Vector3.ZERO); - applyAbsolute.mutX(applyAbsolute.getX() - applyOrigin.getX()); - applyAbsolute.mutY(applyAbsolute.getY() - applyOrigin.getY()); - applyAbsolute.mutZ(applyAbsolute.getZ() - applyOrigin.getZ()); - - Direction newDirection = Direction.findClosest(applyAbsolute, Direction.Flag.CARDINAL | Direction.Flag.ORDINAL | Direction.Flag.SECONDARY_ORDINAL); - - if (newDirection != null) { - Map values = ReflectionUtils.getMap(tag.getValue()); - values.put("Rot", new ByteTag((byte) MCDirections.toRotation(newDirection))); - } - } - return new BaseBlock(transformed, tag); - } - } - return transformed.toBaseBlock(); - } - - public final BlockState transformFast(BlockState block) { - return transform(block, BLOCK_TRANSFORM, transform); - } - - public final BlockState transformFastInverse(BlockState block) { - return transform(block, BLOCK_TRANSFORM_INVERSE, transformInverse); - } - - @Override - public BlockState getLazyBlock(int x, int y, int z) { - return transformFast(super.getLazyBlock(x, y, z)); - } - - @Override - public BlockState getLazyBlock(BlockVector3 position) { - return transformFast(super.getLazyBlock(position)); - } - - @Override - public BlockState getBlock(BlockVector3 position) { - return transformFast(super.getBlock(position)); - } - - @Override - public BiomeType getBiome(BlockVector2 position) { - return super.getBiome(position); - } - - @Override - public boolean setBlock(int x, int y, int z, BlockStateHolder block) throws WorldEditException { - return super.setBlock(x, y, z, transformFastInverse((BlockState) block)); - } - - - @Override - public boolean setBlock(BlockVector3 location, BlockStateHolder block) throws WorldEditException { - return super.setBlock(location, transformFastInverse((BlockState) block)); - } - - -} \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java index 9e62b58b5..23a01009e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java @@ -164,6 +164,7 @@ public class RegionVisitor implements Operation { } catch (FaweException e) { throw new RuntimeException(e); } catch (Throwable ignore) { + ignore.printStackTrace(); } try { while (true) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/session/PasteBuilder.java b/worldedit-core/src/main/java/com/sk89q/worldedit/session/PasteBuilder.java index 5e9ee48b9..bfc82f5c6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/session/PasteBuilder.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/session/PasteBuilder.java @@ -27,7 +27,6 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.transform.BlockTransformExtent; -import com.sk89q.worldedit.extent.transform.BlockTransformExtent2; import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.mask.ExistingBlockMask; import com.sk89q.worldedit.function.mask.Mask; @@ -112,7 +111,7 @@ public class PasteBuilder { public Operation build() { Extent extent = clipboard; if (!transform.isIdentity()) { - extent = new BlockTransformExtent2(extent, transform); + extent = new BlockTransformExtent(extent, transform); } ForwardExtentCopy copy = new ForwardExtentCopy(extent, clipboard.getRegion(), clipboard.getOrigin(), targetExtent, to); copy.setTransform(transform); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/Direction.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/Direction.java index 4363b3c4b..624fbc5aa 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/Direction.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/Direction.java @@ -55,7 +55,13 @@ public enum Direction { EAST_NORTHEAST(Vector3.at(Math.cos(Math.PI / 8), 0, -Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 7, 8), EAST_SOUTHEAST(Vector3.at(Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 6, 9), SOUTH_SOUTHEAST(Vector3.at(Math.sin(Math.PI / 8), 0, Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 6, 9), - SOUTH_SOUTHWEST(Vector3.at(-Math.sin(Math.PI / 8), 0, Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 8, 7); + SOUTH_SOUTHWEST(Vector3.at(-Math.sin(Math.PI / 8), 0, Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 8, 7), + + ASCENDING_NORTH(Vector3.at(0, 1, -1), Flag.ASCENDING_CARDINAL, 3 + 18, 1 + 18), + ASCENDING_EAST(Vector3.at(1, 1, 0), Flag.ASCENDING_CARDINAL, 0 + 18, 2 + 18), + ASCENDING_SOUTH(Vector3.at(0, 1, 1), Flag.ASCENDING_CARDINAL, 1 + 18, 3 + 18), + ASCENDING_WEST(Vector3.at(-1, 1, 0), Flag.ASCENDING_CARDINAL, 2 + 18, 0 + 18), + ; private final Vector3 direction; private final BlockVector3 blockVector; @@ -317,8 +323,9 @@ public enum Direction { public static int ORDINAL = 0x2; public static int SECONDARY_ORDINAL = 0x4; public static int UPRIGHT = 0x8; + public static int ASCENDING_CARDINAL = 0xF; - public static int ALL = CARDINAL | ORDINAL | SECONDARY_ORDINAL | UPRIGHT; + public static int ALL = CARDINAL | ORDINAL | SECONDARY_ORDINAL | UPRIGHT | ASCENDING_CARDINAL; private Flag() { } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java index dbd628aaa..881daff44 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java @@ -47,7 +47,7 @@ import java.util.Objects; * snapshot of blocks correctly, so, for example, the NBT data for a block * may be missing.

*/ -public class BaseBlock implements BlockStateHolder, TileEntityBlock { +public class BaseBlock implements BlockStateHolder { private final BlockState blockState; @Nullable diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockStateHolder.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockStateHolder.java index 598cdba5a..c048ba73e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockStateHolder.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockStateHolder.java @@ -19,16 +19,18 @@ package com.sk89q.worldedit.world.block; +import com.sk89q.worldedit.blocks.TileEntityBlock; import com.sk89q.worldedit.function.pattern.FawePattern; import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.registry.state.PropertyKey; import com.sk89q.worldedit.world.registry.BlockMaterial; +import javax.annotation.Nullable; import java.util.Map; import java.util.stream.Collectors; -public interface BlockStateHolder> extends FawePattern { +public interface BlockStateHolder> extends FawePattern, TileEntityBlock { /** * Get the block type @@ -141,6 +143,51 @@ public interface BlockStateHolder> extends FawePat */ BaseBlock toBaseBlock(CompoundTag compoundTag); + /** + * Return the name of the title entity ID. + * + * @return tile entity ID, non-null string + */ + default String getNbtId() { + return ""; + } + + /** + * Returns whether the block contains NBT data. {@link #getNbtData()} + * must not return null if this method returns true. + * + * @return true if there is NBT data + */ + default boolean hasNbtData() { + return false; + } + + /** + * Get the object's NBT data (tile entity data). The returned tag, if + * modified in any way, should be sent to {@link #setNbtData(CompoundTag)} + * so that the instance knows of the changes. Making changes without + * calling {@link #setNbtData(CompoundTag)} could have unintended + * consequences. + * + *

{@link #hasNbtData()} must return true if and only if method does + * not return null.

+ * + * @return compound tag, or null + */ + @Nullable + default CompoundTag getNbtData() { + return null; + } + + /** + * Set the object's NBT data (tile entity data). + * + * @param nbtData NBT data, or null if no data + */ + default void setNbtData(@Nullable CompoundTag nbtData) { + throw new UnsupportedOperationException("State is immutable"); + } + default String getAsString() { if (getStates().isEmpty()) { return this.getBlockType().getId();