Skip to content

Commit 8bf59a5

Browse files
authored
feat: implement "mutable" transform holder for random rotations (#3173)
* feat: implement "mutable" transform holder for random rotations - also implement random rotation to schematic loadall - "mutable" transform allows random rotation of schematics outside of clipboard brush - fixes #3129 * Alter implementation * Fix parallelism issues * fix: fix assignment
1 parent 145d077 commit 8bf59a5

File tree

10 files changed

+193
-29
lines changed

10 files changed

+193
-29
lines changed

worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/MultiClipboardHolder.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@
33
import com.sk89q.worldedit.extent.clipboard.Clipboard;
44
import com.sk89q.worldedit.session.ClipboardHolder;
55

6+
import javax.annotation.Nonnull;
67
import java.io.IOException;
78
import java.net.URI;
89
import java.util.ArrayList;
910
import java.util.HashSet;
11+
import java.util.Iterator;
1012
import java.util.List;
1113
import java.util.Set;
1214
import java.util.UUID;
1315
import java.util.concurrent.ThreadLocalRandom;
1416

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

17-
public class MultiClipboardHolder extends URIClipboardHolder {
19+
public class MultiClipboardHolder extends URIClipboardHolder implements Iterable<URIClipboardHolder> {
1820

1921
private final List<URIClipboardHolder> holders;
2022
private Clipboard[] cached;
@@ -194,4 +196,9 @@ public void flush() {
194196
}
195197
}
196198

199+
@Override
200+
public @Nonnull Iterator<URIClipboardHolder> iterator() {
201+
return holders.iterator();
202+
}
203+
197204
}

worldedit-core/src/main/java/com/fastasyncworldedit/core/function/generator/SchemGen.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.fastasyncworldedit.core.function.generator;
22

33
import com.fastasyncworldedit.core.math.MutableBlockVector3;
4+
import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
45
import com.sk89q.worldedit.WorldEditException;
56
import com.sk89q.worldedit.extent.Extent;
67
import com.sk89q.worldedit.extent.clipboard.Clipboard;
@@ -92,7 +93,7 @@ public boolean spawn(Random random, int x, int z) throws WorldEditException {
9293
if (transform.isIdentity()) {
9394
clipboard.paste(extent, mutable, false);
9495
} else {
95-
clipboard.paste(extent, mutable, false, transform);
96+
clipboard.paste(extent, mutable, false, MutatingOperationTransformHolder.transform(transform, true));
9697
}
9798
mutable.mutY(y);
9899
return true;

worldedit-core/src/main/java/com/fastasyncworldedit/core/function/pattern/RandomFullClipboardPattern.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.fastasyncworldedit.core.function.pattern;
22

33
import com.fastasyncworldedit.core.math.MutableBlockVector3;
4+
import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
45
import com.sk89q.worldedit.WorldEditException;
56
import com.sk89q.worldedit.extent.Extent;
67
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
@@ -67,6 +68,7 @@ public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws W
6768
if (newTransform.isIdentity()) {
6869
clipboard.paste(extent, set, false);
6970
} else {
71+
newTransform = MutatingOperationTransformHolder.transform(newTransform, true);
7072
clipboard.paste(extent, set, false, newTransform);
7173
}
7274
return true;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.fastasyncworldedit.core.math.transform;
2+
3+
import com.sk89q.worldedit.math.Vector3;
4+
import com.sk89q.worldedit.math.transform.CombinedTransform;
5+
import com.sk89q.worldedit.math.transform.Transform;
6+
7+
import java.util.function.Function;
8+
9+
/**
10+
* Applies an operation on a {@link Transform} when {@link MutatingOperationTransformHolder#mutate()} is called. Typically at
11+
* the start of an operation.
12+
*
13+
* @param <T> transform to mutate type
14+
* @since TODO
15+
*/
16+
public class MutatingOperationTransformHolder<T extends Transform> implements Transform {
17+
18+
private final Function<? super T, ? extends T> operation;
19+
private T transform;
20+
21+
/**
22+
* Construct new instance
23+
*
24+
* @param transform transform to mutate
25+
* @since TODO
26+
*/
27+
public MutatingOperationTransformHolder(T transform, Function<? super T, ? extends T> operation) {
28+
this.transform = transform;
29+
this.operation = operation;
30+
}
31+
32+
/**
33+
* Perform a mutation on the given transform, if supported. Else, does nothing. This mutation will depend on the specific
34+
* transform implementation.
35+
* <p>
36+
* Implementation detail: it may be possible for this method to be called multiple times before an operation actually occurs.
37+
*
38+
* @param transform Transform to transform
39+
* @since TODO
40+
*/
41+
public static Transform transform(Transform transform) {
42+
return transform(transform, false);
43+
}
44+
45+
/**
46+
* Perform a mutation on the given transform, if supported. Else, does nothing. This mutation will depend on the specific
47+
* transform implementation.
48+
* <p>
49+
* Implementation detail: it may be possible for this method to be called multiple times before an operation actually occurs.
50+
*
51+
* @param transform Transform to transform
52+
* @param parallel If the context is potentially parallel, meaning the given transform will be copied if required
53+
* @since TODO
54+
*/
55+
public static Transform transform(Transform transform, boolean parallel) {
56+
return switch (transform) {
57+
case MutatingOperationTransformHolder<?> mutating -> parallel ? mutating.copy().mutate() : mutating.mutate();
58+
case CombinedTransform combined -> {
59+
if (!parallel) {
60+
combined.getTransforms().forEach(t -> transform(t, false));
61+
yield combined;
62+
}
63+
yield new CombinedTransform(combined.getTransforms().stream().map(t -> transform(t, true)).toList());
64+
}
65+
default -> transform;
66+
};
67+
}
68+
69+
/**
70+
* Apply the mutator to the transform
71+
*
72+
* @since TODO
73+
*/
74+
public MutatingOperationTransformHolder<T> mutate() {
75+
if (operation != null) {
76+
transform = operation.apply(transform);
77+
}
78+
return this;
79+
}
80+
81+
@Override
82+
public boolean isIdentity() {
83+
return transform.isIdentity();
84+
}
85+
86+
@Override
87+
public Vector3 apply(final Vector3 input) {
88+
return transform.apply(input);
89+
}
90+
91+
@Override
92+
public Transform inverse() {
93+
return transform.inverse();
94+
}
95+
96+
@Override
97+
public Transform combine(final Transform other) {
98+
return transform.combine(other);
99+
}
100+
101+
public MutatingOperationTransformHolder<T> copy() {
102+
// I don't think we should see nested mutating, but you never know
103+
T transform = this.transform instanceof MutatingOperationTransformHolder<?> mutating ? (T) mutating.copy() :
104+
this.transform;
105+
return new MutatingOperationTransformHolder<>(transform, operation);
106+
}
107+
108+
}

worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.fastasyncworldedit.core.extent.clipboard.WorldCopyClipboard;
3333
import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream;
3434
import com.fastasyncworldedit.core.limit.FaweLimit;
35+
import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
3536
import com.fastasyncworldedit.core.util.ImgurUtility;
3637
import com.fastasyncworldedit.core.util.MainUtil;
3738
import com.fastasyncworldedit.core.util.MaskTraverser;
@@ -404,7 +405,7 @@ public void run(OutputStream out) {
404405
final Clipboard target;
405406
// If we have a transform, bake it into the copy
406407
if (!transform.isIdentity()) {
407-
target = clipboard.transform(transform);
408+
target = clipboard.transform(MutatingOperationTransformHolder.transform(transform));
408409
} else {
409410
target = clipboard;
410411
}
@@ -472,9 +473,9 @@ public void place(
472473
Region region = clipboard.getRegion().clone();
473474
if (selectPasted || onlySelect || removeEntities) {
474475
BlockVector3 clipboardOffset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
475-
BlockVector3 realTo = to.add(holder.getTransform().apply(clipboardOffset.toVector3()).toBlockPoint());
476-
BlockVector3 max = realTo.add(holder
477-
.getTransform()
476+
Transform transform = MutatingOperationTransformHolder.transform(holder.getTransform());
477+
BlockVector3 realTo = to.add(transform.apply(clipboardOffset.toVector3()).toBlockPoint());
478+
BlockVector3 max = realTo.add(transform
478479
.apply(region.getMaximumPoint().subtract(region.getMinimumPoint()).toVector3())
479480
.toBlockPoint());
480481
if (removeEntities) {
@@ -564,10 +565,9 @@ public void paste(
564565

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

572572
// FAWE start - entity remova;l
573573
if (removeEntities) {

worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import com.fastasyncworldedit.core.event.extent.ActorSaveClipboardEvent;
2525
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
2626
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
27-
import com.fastasyncworldedit.core.extent.clipboard.io.schematic.MinecraftStructure;
27+
import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
2828
import com.fastasyncworldedit.core.util.MainUtil;
2929
import com.google.common.collect.Multimap;
3030
import com.sk89q.worldedit.LocalConfiguration;
@@ -158,9 +158,11 @@ public void loadall(
158158
@Arg(desc = "File name.")
159159
String filename,
160160
@Switch(name = 'o', desc = "Overwrite/replace existing clipboard(s)")
161-
boolean overwrite
162-
// @Switch(name = 'r', desc = "Apply random rotation") <- not implemented below.
163-
// boolean randomRotate
161+
boolean overwrite,
162+
@Switch(name = 'r', desc = "Apply random rotation (static by default)")
163+
boolean randomRotate,
164+
@Switch(name = 'd', desc = "Random rotation is dynamic, changing each use")
165+
boolean dynamicRandom
164166
) throws FilenameException {
165167
final ClipboardFormat format = ClipboardFormats.findByAlias(formatName);
166168
if (format == null) {
@@ -169,19 +171,44 @@ public void loadall(
169171
}
170172
try {
171173
MultiClipboardHolder all = ClipboardFormats.loadAllFromInput(actor, filename, null, true);
172-
if (all != null) {
173-
if (overwrite) {
174-
session.setClipboard(all);
175-
} else {
176-
session.addClipboard(all);
177-
}
174+
if (all == null) {
178175
actor.print(Caption.of("fawe.worldedit.schematic.schematic.loaded", filename));
176+
return;
177+
}
178+
if (randomRotate) {
179+
for (ClipboardHolder holder : all) {
180+
setRandomRotateTransform(dynamicRandom, holder);
181+
}
182+
setRandomRotateTransform(dynamicRandom, all);
183+
}
184+
if (overwrite) {
185+
session.setClipboard(all);
186+
} else {
187+
session.addClipboard(all);
179188
}
189+
actor.print(Caption.of("fawe.worldedit.schematic.schematic.loaded", filename));
180190
} catch (IOException e) {
181191
throw new RuntimeException(e);
182192
}
183193
}
184194

195+
private static void setRandomRotateTransform(boolean dynamicRandom, ClipboardHolder holder) {
196+
if (dynamicRandom) {
197+
MutatingOperationTransformHolder<AffineTransform> mutating = new MutatingOperationTransformHolder<>(
198+
new AffineTransform(), t -> {
199+
int rotate = 90 * ThreadLocalRandom.current().nextInt(4);
200+
return t.rotateY(rotate);
201+
}
202+
);
203+
holder.setTransform(mutating);
204+
} else {
205+
AffineTransform transform = new AffineTransform();
206+
int rotate = 90 * ThreadLocalRandom.current().nextInt(4);
207+
transform = transform.rotateY(rotate);
208+
holder.setTransform(transform);
209+
}
210+
}
211+
185212
@Command(
186213
name = "clear",
187214
desc = "Clear your clipboard"
@@ -319,7 +346,9 @@ public void load(
319346
@Arg(desc = "Format name.", def = "")
320347
String formatName,
321348
@Switch(name = 'r', desc = "Apply random rotation to the clipboard")
322-
boolean randomRotate
349+
boolean randomRotate,
350+
@Switch(name = 'd', desc = "Random rotation is dynamic, changing each use")
351+
boolean dynamicRandom
323352
//FAWE end
324353
) throws FilenameException {
325354
LocalConfiguration config = worldEdit.getConfiguration();
@@ -435,10 +464,7 @@ public void load(
435464
closer.register(in);
436465
format.hold(actor, uri, in);
437466
if (randomRotate) {
438-
AffineTransform transform = new AffineTransform();
439-
int rotate = 90 * ThreadLocalRandom.current().nextInt(4);
440-
transform = transform.rotateY(rotate);
441-
session.getClipboard().setTransform(transform);
467+
setRandomRotateTransform(dynamicRandom, session.getClipboard());
442468
}
443469
actor.print(Caption.of("fawe.worldedit.schematic.schematic.loaded", filename));
444470
} catch (IllegalArgumentException e) {
@@ -888,7 +914,7 @@ private abstract static class SchematicOutputTask<T> implements Callable<T> {
888914

889915
protected void writeToOutputStream(OutputStream outputStream) throws IOException, WorldEditException {
890916
Clipboard clipboard = holder.getClipboard();
891-
Transform transform = holder.getTransform();
917+
Transform transform = MutatingOperationTransformHolder.transform(holder.getTransform()); //FAWE: mutate transform
892918
Clipboard target = clipboard.transform(transform);
893919

894920
try (Closer closer = Closer.create()) {
@@ -924,7 +950,7 @@ private static class SchematicSaveTask extends SchematicOutputTask<Void> {
924950
@Override
925951
public Void call() throws Exception {
926952
Clipboard clipboard = holder.getClipboard();
927-
Transform transform = holder.getTransform();
953+
Transform transform = MutatingOperationTransformHolder.transform(holder.getTransform()); //FAWE - mutating transform
928954
Clipboard target;
929955

930956
//FAWE start

worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/FactoryConverter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.fastasyncworldedit.core.extent.ResettableExtent;
2323
import com.fastasyncworldedit.core.extent.SupplyingExtent;
24+
import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
2425
import com.sk89q.worldedit.EmptyClipboardException;
2526
import com.sk89q.worldedit.LocalSession;
2627
import com.sk89q.worldedit.WorldEdit;
@@ -97,7 +98,7 @@ public static void register(WorldEdit worldEdit, CommandManager commandManager)
9798
context -> {
9899
try {
99100
ClipboardHolder holder = context.getSession().getClipboard();
100-
Transform transform = holder.getTransform();
101+
Transform transform = MutatingOperationTransformHolder.transform(holder.getTransform()); //FAWE: mutate transform
101102
Extent target;
102103
if (transform.isIdentity()) {
103104
target = holder.getClipboard();

worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/brush/ClipboardBrush.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

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

22+
import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
2223
import com.sk89q.worldedit.EditSession;
2324
import com.sk89q.worldedit.MaxChangedBlocksException;
2425
import com.sk89q.worldedit.extent.clipboard.Clipboard;
@@ -90,7 +91,7 @@ public void build(EditSession editSession, BlockVector3 position, Pattern patter
9091
int rotate = 90 * ThreadLocalRandom.current().nextInt(4);
9192
transform = ((AffineTransform) transform).rotateY(rotate);
9293
if (originalTransform != null) {
93-
transform = originalTransform.combine(transform);
94+
transform = originalTransform.combine(MutatingOperationTransformHolder.transform(originalTransform, true));
9495
}
9596
}
9697
holder.setTransform(transform);

worldedit-core/src/main/java/com/sk89q/worldedit/math/transform/CombinedTransform.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package com.sk89q.worldedit.math.transform;
2121

22+
import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder;
2223
import com.sk89q.worldedit.math.Vector3;
2324

2425
import java.util.ArrayList;
@@ -96,4 +97,18 @@ public Transform combine(Transform other) {
9697
}
9798
}
9899

100+
//FAWE start
101+
102+
/**
103+
* Get an unmodifiable view of the transforms stored by this {@link CombinedTransform}. Individuable transforms may be
104+
* mutable ({@link MutatingOperationTransformHolder})
105+
*
106+
* @return view of held transforms
107+
* @since TODO
108+
*/
109+
public List<Transform> getTransforms() {
110+
return List.of(transforms);
111+
}
112+
//FAWE end
113+
99114
}

0 commit comments

Comments
 (0)