Skip to content

feat: implement "mutable" transform holder for random rotations #3173

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

Merged
merged 4 commits into from
Jun 8, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.session.ClipboardHolder;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;

import static com.google.common.base.Preconditions.checkNotNull;

public class MultiClipboardHolder extends URIClipboardHolder {
public class MultiClipboardHolder extends URIClipboardHolder implements Iterable<URIClipboardHolder> {

private final List<URIClipboardHolder> holders;
private Clipboard[] cached;
Expand Down Expand Up @@ -194,4 +196,9 @@ public void flush() {
}
}

@Override
public @Nonnull Iterator<URIClipboardHolder> iterator() {
return holders.iterator();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.fastasyncworldedit.core.function.generator;

import com.fastasyncworldedit.core.math.MutableBlockVector3;
import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
Expand Down Expand Up @@ -92,7 +93,7 @@ public boolean spawn(Random random, int x, int z) throws WorldEditException {
if (transform.isIdentity()) {
clipboard.paste(extent, mutable, false);
} else {
clipboard.paste(extent, mutable, false, transform);
clipboard.paste(extent, mutable, false, MutatingOperationTransformHolder.transform(transform, true));
}
mutable.mutY(y);
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.fastasyncworldedit.core.function.pattern;

import com.fastasyncworldedit.core.math.MutableBlockVector3;
import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
Expand Down Expand Up @@ -67,6 +68,7 @@ public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws W
if (newTransform.isIdentity()) {
clipboard.paste(extent, set, false);
} else {
MutatingOperationTransformHolder.transform(newTransform, true);
clipboard.paste(extent, set, false, newTransform);
}
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.fastasyncworldedit.core.math.transform;

import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.math.transform.CombinedTransform;
import com.sk89q.worldedit.math.transform.Transform;

import java.util.function.Function;

/**
* Applies an operation on a {@link Transform} when {@link MutatingOperationTransformHolder#mutate()} is called. Typically at
* the start of an operation.
*
* @param <T> transform to mutate type
* @since TODO
*/
public class MutatingOperationTransformHolder<T extends Transform> implements Transform {

private final Function<? super T, ? extends T> operation;
private T transform;

/**
* Construct new instance
*
* @param transform transform to mutate
* @since TODO
*/
public MutatingOperationTransformHolder(T transform, Function<? super T, ? extends T> operation) {
this.transform = transform;
this.operation = operation;
}

/**
* Perform a mutation on the given transform, if supported. Else, does nothing. This mutation will depend on the specific
* transform implementation.
* <p>
* Implementation detail: it may be possible for this method to be called multiple times before an operation actually occurs.
*
* @param transform Transform to transform
* @since TODO
*/
public static Transform transform(Transform transform) {
return transform(transform, false);
}

/**
* Perform a mutation on the given transform, if supported. Else, does nothing. This mutation will depend on the specific
* transform implementation.
* <p>
* Implementation detail: it may be possible for this method to be called multiple times before an operation actually occurs.
*
* @param transform Transform to transform
* @param parallel If the context is potentially parallel, meaning the given transform will be copied if required
* @since TODO
*/
public static Transform transform(Transform transform, boolean parallel) {
return switch (transform) {
case MutatingOperationTransformHolder<?> mutating -> parallel ? mutating.copy().mutate() : mutating.mutate();
case CombinedTransform combined -> {
if (!parallel) {
combined.getTransforms().forEach(t -> transform(t, false));
yield combined;
}
yield new CombinedTransform(combined.getTransforms().stream().map(t -> transform(t, true)).toList());
}
default -> transform;
};
}

/**
* Apply the mutator to the transform
*
* @since TODO
*/
public MutatingOperationTransformHolder<T> mutate() {
if (operation != null) {
transform = operation.apply(transform);
}
return this;
}

@Override
public boolean isIdentity() {
return transform.isIdentity();
}

@Override
public Vector3 apply(final Vector3 input) {
return transform.apply(input);
}

@Override
public Transform inverse() {
return transform.inverse();
}

@Override
public Transform combine(final Transform other) {
return transform.combine(other);
}

public MutatingOperationTransformHolder<T> copy() {
// I don't think we should see nested mutating, but you never know
T transform = this.transform instanceof MutatingOperationTransformHolder<?> mutating ? (T) mutating.copy() :
this.transform;
return new MutatingOperationTransformHolder<>(transform, operation);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.fastasyncworldedit.core.extent.clipboard.WorldCopyClipboard;
import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
import com.fastasyncworldedit.core.util.ImgurUtility;
import com.fastasyncworldedit.core.util.MainUtil;
import com.fastasyncworldedit.core.util.MaskTraverser;
Expand Down Expand Up @@ -404,7 +405,7 @@ public void run(OutputStream out) {
final Clipboard target;
// If we have a transform, bake it into the copy
if (!transform.isIdentity()) {
target = clipboard.transform(transform);
target = clipboard.transform(MutatingOperationTransformHolder.transform(transform));
} else {
target = clipboard;
}
Expand Down Expand Up @@ -472,9 +473,9 @@ public void place(
Region region = clipboard.getRegion().clone();
if (selectPasted || onlySelect || removeEntities) {
BlockVector3 clipboardOffset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
BlockVector3 realTo = to.add(holder.getTransform().apply(clipboardOffset.toVector3()).toBlockPoint());
BlockVector3 max = realTo.add(holder
.getTransform()
Transform transform = MutatingOperationTransformHolder.transform(holder.getTransform());
BlockVector3 realTo = to.add(transform.apply(clipboardOffset.toVector3()).toBlockPoint());
BlockVector3 max = realTo.add(transform
.apply(region.getMaximumPoint().subtract(region.getMinimumPoint()).toVector3())
.toBlockPoint());
if (removeEntities) {
Expand Down Expand Up @@ -564,10 +565,9 @@ public void paste(

if (selectPasted || onlySelect || removeEntities) {
BlockVector3 clipboardOffset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
Vector3 realTo = to.toVector3().add(holder.getTransform().apply(clipboardOffset.toVector3()));
Vector3 max = realTo.add(holder
.getTransform()
.apply(region.getMaximumPoint().subtract(region.getMinimumPoint()).toVector3()));
Transform transform = MutatingOperationTransformHolder.transform(holder.getTransform()); //FAWE: mutate transform
Vector3 realTo = to.toVector3().add(transform.apply(clipboardOffset.toVector3()));
Vector3 max = realTo.add(transform.apply(region.getMaximumPoint().subtract(region.getMinimumPoint()).toVector3()));

// FAWE start - entity remova;l
if (removeEntities) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import com.fastasyncworldedit.core.event.extent.ActorSaveClipboardEvent;
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
import com.fastasyncworldedit.core.extent.clipboard.io.schematic.MinecraftStructure;
import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
import com.fastasyncworldedit.core.util.MainUtil;
import com.google.common.collect.Multimap;
import com.sk89q.worldedit.LocalConfiguration;
Expand Down Expand Up @@ -158,9 +158,11 @@ public void loadall(
@Arg(desc = "File name.")
String filename,
@Switch(name = 'o', desc = "Overwrite/replace existing clipboard(s)")
boolean overwrite
// @Switch(name = 'r', desc = "Apply random rotation") <- not implemented below.
// boolean randomRotate
boolean overwrite,
@Switch(name = 'r', desc = "Apply random rotation (static by default)")
boolean randomRotate,
@Switch(name = 'd', desc = "Random rotation is dynamic, changing each use")
boolean dynamicRandom
) throws FilenameException {
final ClipboardFormat format = ClipboardFormats.findByAlias(formatName);
if (format == null) {
Expand All @@ -169,19 +171,44 @@ public void loadall(
}
try {
MultiClipboardHolder all = ClipboardFormats.loadAllFromInput(actor, filename, null, true);
if (all != null) {
if (overwrite) {
session.setClipboard(all);
} else {
session.addClipboard(all);
}
if (all == null) {
actor.print(Caption.of("fawe.worldedit.schematic.schematic.loaded", filename));
return;
}
if (randomRotate) {
for (ClipboardHolder holder : all) {
setRandomRotateTransform(dynamicRandom, holder);
}
setRandomRotateTransform(dynamicRandom, all);
}
if (overwrite) {
session.setClipboard(all);
} else {
session.addClipboard(all);
}
actor.print(Caption.of("fawe.worldedit.schematic.schematic.loaded", filename));
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private static void setRandomRotateTransform(boolean dynamicRandom, ClipboardHolder holder) {
if (dynamicRandom) {
MutatingOperationTransformHolder<AffineTransform> mutating = new MutatingOperationTransformHolder<>(
new AffineTransform(), t -> {
int rotate = 90 * ThreadLocalRandom.current().nextInt(4);
return t.rotateY(rotate);
}
);
holder.setTransform(mutating);
} else {
AffineTransform transform = new AffineTransform();
int rotate = 90 * ThreadLocalRandom.current().nextInt(4);
transform = transform.rotateY(rotate);
holder.setTransform(transform);
}
}

@Command(
name = "clear",
desc = "Clear your clipboard"
Expand Down Expand Up @@ -319,7 +346,9 @@ public void load(
@Arg(desc = "Format name.", def = "")
String formatName,
@Switch(name = 'r', desc = "Apply random rotation to the clipboard")
boolean randomRotate
boolean randomRotate,
@Switch(name = 'd', desc = "Random rotation is dynamic, changing each use")
boolean dynamicRandom
//FAWE end
) throws FilenameException {
LocalConfiguration config = worldEdit.getConfiguration();
Expand Down Expand Up @@ -435,10 +464,7 @@ public void load(
closer.register(in);
format.hold(actor, uri, in);
if (randomRotate) {
AffineTransform transform = new AffineTransform();
int rotate = 90 * ThreadLocalRandom.current().nextInt(4);
transform = transform.rotateY(rotate);
session.getClipboard().setTransform(transform);
setRandomRotateTransform(dynamicRandom, session.getClipboard());
}
actor.print(Caption.of("fawe.worldedit.schematic.schematic.loaded", filename));
} catch (IllegalArgumentException e) {
Expand Down Expand Up @@ -888,7 +914,7 @@ private abstract static class SchematicOutputTask<T> implements Callable<T> {

protected void writeToOutputStream(OutputStream outputStream) throws IOException, WorldEditException {
Clipboard clipboard = holder.getClipboard();
Transform transform = holder.getTransform();
Transform transform = MutatingOperationTransformHolder.transform(holder.getTransform()); //FAWE: mutate transform
Clipboard target = clipboard.transform(transform);

try (Closer closer = Closer.create()) {
Expand Down Expand Up @@ -924,7 +950,7 @@ private static class SchematicSaveTask extends SchematicOutputTask<Void> {
@Override
public Void call() throws Exception {
Clipboard clipboard = holder.getClipboard();
Transform transform = holder.getTransform();
Transform transform = MutatingOperationTransformHolder.transform(holder.getTransform()); //FAWE - mutating transform
Clipboard target;

//FAWE start
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import com.fastasyncworldedit.core.extent.ResettableExtent;
import com.fastasyncworldedit.core.extent.SupplyingExtent;
import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
import com.sk89q.worldedit.EmptyClipboardException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
Expand Down Expand Up @@ -97,7 +98,7 @@ public static void register(WorldEdit worldEdit, CommandManager commandManager)
context -> {
try {
ClipboardHolder holder = context.getSession().getClipboard();
Transform transform = holder.getTransform();
Transform transform = MutatingOperationTransformHolder.transform(holder.getTransform()); //FAWE: mutate transform
Extent target;
if (transform.isIdentity()) {
target = holder.getClipboard();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package com.sk89q.worldedit.command.tool.brush;

import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
Expand Down Expand Up @@ -90,7 +91,7 @@ public void build(EditSession editSession, BlockVector3 position, Pattern patter
int rotate = 90 * ThreadLocalRandom.current().nextInt(4);
transform = ((AffineTransform) transform).rotateY(rotate);
if (originalTransform != null) {
transform = originalTransform.combine(transform);
transform = originalTransform.combine(MutatingOperationTransformHolder.transform(originalTransform, true));
}
}
holder.setTransform(transform);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package com.sk89q.worldedit.math.transform;

import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
import com.sk89q.worldedit.math.Vector3;

import java.util.ArrayList;
Expand Down Expand Up @@ -96,4 +97,18 @@ public Transform combine(Transform other) {
}
}

//FAWE start

/**
* Get an unmodifiable view of the transforms stored by this {@link CombinedTransform}. Individuable transforms may be
* mutable ({@link MutatingOperationTransformHolder})
*
* @return view of held transforms
* @since TODO
*/
public List<Transform> getTransforms() {
return List.of(transforms);
}
//FAWE end

}
Loading
Loading