Implement a "sensitivity" setting, a mask, and an option to only distinguish air vs blocks to blendball (#1832)

This commit is contained in:
Jordan 2022-06-22 12:50:22 +01:00 committed by GitHub
parent d498996cbd
commit 100288ada5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 14 deletions

View File

@ -1,5 +1,7 @@
package com.fastasyncworldedit.core.command.tool.brush; package com.fastasyncworldedit.core.command.tool.brush;
import com.fastasyncworldedit.core.function.mask.CachedMask;
import com.fastasyncworldedit.core.math.MutableBlockVector3;
import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.command.tool.brush.Brush; import com.sk89q.worldedit.command.tool.brush.Brush;
@ -8,10 +10,40 @@ import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.block.BlockTypes;
import javax.annotation.Nullable;
import java.util.Arrays; import java.util.Arrays;
public class BlendBall implements Brush { public class BlendBall implements Brush {
private static final BlockState AIR = BlockTypes.AIR.getDefaultState();
private final int minFreqDiff;
private final boolean onlyAir;
@Nullable private final CachedMask mask;
/**
* Create a new {@link Brush} instance with default settings.
*/
public BlendBall() {
this(1, false, null);
}
/**
* Create a new {@link Brush} instance.
*
* @param minFreqDiff Minimum difference between nearby blocks (3x3x3 cuboid centered on position) to alter the current block
* at a position
* @param onlyAir Only consider air for comparing existing blocks, and for altering existing blocks
* @param mask Mask to limit the blocks being considered for alteration. Will also limit blocks types able to be
* placed, and will consider blocks not meeting the mask as air
* @since TODO
*/
public BlendBall(int minFreqDiff, boolean onlyAir, @Nullable CachedMask mask) {
this.minFreqDiff = minFreqDiff;
this.onlyAir = onlyAir;
this.mask = mask;
}
@Override @Override
public void build(EditSession editSession, BlockVector3 position, Pattern pattern, double size) throws public void build(EditSession editSession, BlockVector3 position, Pattern pattern, double size) throws
MaxChangedBlocksException { MaxChangedBlocksException {
@ -27,41 +59,75 @@ public class BlendBall implements Brush {
int maxY = editSession.getMaxY(); int maxY = editSession.getMaxY();
int minY = editSession.getMinY(); int minY = editSession.getMinY();
MutableBlockVector3 mutable = new MutableBlockVector3();
for (int x = -outsetSize; x <= outsetSize; x++) { for (int x = -outsetSize; x <= outsetSize; x++) {
int x0 = x + tx; int x0 = x + tx;
int xx = x * x;
for (int y = -outsetSize; y <= outsetSize; y++) { for (int y = -outsetSize; y <= outsetSize; y++) {
int y0 = y + ty; int y0 = y + ty;
for (int z = -outsetSize; z <= outsetSize; z++) { if (y0 + 1 < minY || y0 - 1 > maxY) {
if (x * x + y * y + z * z >= brushSizeSquared) {
continue; continue;
} }
int yy = y * y;
int xxyy = xx + yy;
if (xxyy >= brushSizeSquared) {
continue;
}
for (int z = -outsetSize; z <= outsetSize; z++) {
int z0 = z + tz; int z0 = z + tz;
int highest = 1; if (xxyy + z * z >= brushSizeSquared || maskFails(editSession, mutable.setComponents(x0, y0, z0))) {
continue;
}
int highest = 1, currentBlockFrequency = 1;
BlockState currentState = editSession.getBlock(x0, y0, z0); BlockState currentState = editSession.getBlock(x0, y0, z0);
BlockState highestState = currentState; BlockState highestState = currentState;
int currentStateID = currentState.getInternalBlockTypeId();
Arrays.fill(frequency, 0); Arrays.fill(frequency, 0);
int air = 0;
int total = 26;
boolean tie = false; boolean tie = false;
for (int ox = -1; ox <= 1; ox++) { for (int ox = -1; ox <= 1; ox++) {
for (int oz = -1; oz <= 1; oz++) { for (int oz = -1; oz <= 1; oz++) {
for (int oy = -1; oy <= 1; oy++) { for (int oy = -1; oy <= 1; oy++) {
if (oy + y0 < minY || oy + y0 > maxY) { if (ox == 0 && oy == 0 && oz == 0) {
continue;
} else if (oy + y0 < minY || oy + y0 > maxY) {
total--;
continue; continue;
} }
BlockState state = editSession.getBlock(x0 + ox, y0 + oy, z0 + oz); boolean masked = maskFails(editSession, mutable.setComponents(x0 + ox, y0 + oy, z0 + oz));
int count = frequency[state.getInternalBlockTypeId()]; BlockState state = masked ? AIR : editSession.getBlock(x0 + ox, y0 + oy, z0 + oz);
if (state.getBlockType().getMaterial().isAir()) {
air++;
}
int internalID = state.getInternalBlockTypeId();
int count = frequency[internalID];
if (internalID == currentStateID) {
currentBlockFrequency++;
}
count++; count++;
if (count > highest) { if (count - highest >= minFreqDiff) {
highest = count; highest = count;
highestState = state; highestState = state;
tie = false; tie = false;
} else if (count == highest) { } else if (count == highest) {
tie = true; tie = true;
} }
frequency[state.getInternalBlockTypeId()] = count; frequency[internalID] = count;
} }
} }
} }
if (!tie && currentState != highestState) { if (onlyAir) {
if (air * 2 - total >= minFreqDiff) {
if (!currentState.isAir()) {
editSession.setBlock(x0, y0, z0, AIR);
}
} else if (currentState.isAir() && total - 2 * air >= minFreqDiff) {
editSession.setBlock(x0, y0, z0, highestState);
}
continue;
}
if (highest - currentBlockFrequency >= minFreqDiff && !tie && currentState != highestState) {
editSession.setBlock(x0, y0, z0, highestState); editSession.setBlock(x0, y0, z0, highestState);
} }
} }
@ -69,5 +135,8 @@ public class BlendBall implements Brush {
} }
} }
private boolean maskFails(EditSession editSession, MutableBlockVector3 mutable) {
return mask == null || !mask.test(editSession, mutable);
}
} }

View File

@ -1,6 +1,8 @@
package com.fastasyncworldedit.core.function.mask; package com.fastasyncworldedit.core.function.mask;
import com.fastasyncworldedit.core.math.BlockVectorSet; import com.fastasyncworldedit.core.math.BlockVectorSet;
import com.fastasyncworldedit.core.math.LocalBlockVectorSet;
import com.fastasyncworldedit.core.util.collection.BlockVector3Set;
import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.mask.AbstractExtentMask; import com.sk89q.worldedit.function.mask.AbstractExtentMask;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
@ -11,12 +13,30 @@ import javax.annotation.Nullable;
public class CachedMask extends AbstractDelegateMask implements ResettableMask { public class CachedMask extends AbstractDelegateMask implements ResettableMask {
private final boolean hasExtent; private final boolean hasExtent;
private transient BlockVectorSet cache_checked = new BlockVectorSet(); private transient BlockVector3Set cache_checked;
private transient BlockVectorSet cache_results = new BlockVectorSet(); private transient BlockVector3Set cache_results;
public CachedMask(Mask mask) { public CachedMask(Mask mask) {
this(mask, false);
}
/**
* Create a new CachedMask instance for the given mask
*
* @param mask Mask to cache results of
* @param local If the area will be small
* @since TODO
*/
public CachedMask(Mask mask, boolean local) {
super(mask); super(mask);
hasExtent = mask instanceof AbstractExtentMask; hasExtent = mask instanceof AbstractExtentMask;
if (local) {
cache_checked = new LocalBlockVectorSet();
cache_results = new LocalBlockVectorSet();
} else {
cache_checked = new BlockVectorSet();
cache_results = new BlockVectorSet();
}
} }
public static CachedMask cache(Mask mask) { public static CachedMask cache(Mask mask) {
@ -95,7 +115,7 @@ public class CachedMask extends AbstractDelegateMask implements ResettableMask {
@Override @Override
public Mask copy() { public Mask copy() {
return new CachedMask(getMask().copy()); return new CachedMask(getMask().copy(), cache_checked instanceof LocalBlockVectorSet);
} }
} }

View File

@ -50,6 +50,7 @@ import com.fastasyncworldedit.core.command.tool.sweep.SweepBrush;
import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder; import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
import com.fastasyncworldedit.core.function.mask.CachedMask;
import com.fastasyncworldedit.core.function.mask.IdMask; import com.fastasyncworldedit.core.function.mask.IdMask;
import com.fastasyncworldedit.core.function.mask.SingleBlockTypeMask; import com.fastasyncworldedit.core.function.mask.SingleBlockTypeMask;
import com.fastasyncworldedit.core.limit.FaweLimit; import com.fastasyncworldedit.core.limit.FaweLimit;
@ -138,6 +139,7 @@ import java.nio.file.FileSystems;
import java.util.List; import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.sk89q.worldedit.internal.command.CommandUtil.checkCommandArgument;
/** /**
* Commands to set brush shape. * Commands to set brush shape.
@ -175,10 +177,20 @@ public class BrushCommands {
public void blendBallBrush( public void blendBallBrush(
InjectedValueAccess context, InjectedValueAccess context,
@Arg(desc = "The radius to sample for blending", def = "5") @Arg(desc = "The radius to sample for blending", def = "5")
Expression radius Expression radius,
@Arg(desc = "Minimum difference in frequency to change block", def = "1")
int minFreqDiff,
@Switch(name = 'a', desc = "Compare only air vs existing blocks")
boolean onlyAir,
@ArgFlag(name = 'm', desc = "Mask to limit blocks being considered", def = "")
Mask mask
) throws WorldEditException { ) throws WorldEditException {
worldEdit.checkMaxBrushRadius(radius); worldEdit.checkMaxBrushRadius(radius);
set(context, new BlendBall(), "worldedit.brush.blendball").setSize(radius); checkCommandArgument(minFreqDiff >= 0 && minFreqDiff <= 26, "minFreqDiff not in range 0 <= value <= 26");
if (mask != null && !(mask instanceof CachedMask)) {
mask = new CachedMask(mask, false);
}
set(context, new BlendBall(minFreqDiff, onlyAir, (CachedMask) mask), "worldedit.brush.blendball").setSize(radius);
} }
@Command( @Command(