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

Odd summetry fix take2 #405

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 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: 19 additions & 6 deletions shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,13 @@ protected BooleanMask setSizeInternal(int newSize) {
} else if (oldSize != newSize) {
long[] oldMask = mask;
initializeMask(newSize);
Map<Integer, Integer> coordinateMap = getSymmetricScalingCoordinateMap(oldSize, newSize);
applyWithSymmetry(SymmetryType.SPAWN, (x, y) -> {
boolean value = getBit(coordinateMap.get(x), coordinateMap.get(y), oldSize, oldMask);
applyAtSymmetryPoints(x, y, SymmetryType.SPAWN, (sx, sy) -> setPrimitive(sx, sy, value));

float scale = (float)oldSize / (float)newSize;

apply((x, y) -> {
int sx = (int)(x * scale);
int sy = (int)(y * scale);
setPrimitive(x, y, getBit(sx, sy, oldSize, oldMask));
Copy link
Member

Choose a reason for hiding this comment

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

One note here is that we still need the symmetricScalingCoordinateMap, otherwise the map will end up with a one pixel shift in some parts.

This is because the conversion from float to int is not symmetric around the center of the map. Sof for example with a ratio of .5 11->5 while (100-11)->(50-5.5)->(44.5)->44 when really we would want it to go to 45 to have the same distance from the edge assuming a 100 pixel size map to a 50 pixel size map.

Copy link
Contributor Author

@clivepaterson clivepaterson Jul 13, 2024

Choose a reason for hiding this comment

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

Oh yep, I see what you mean.
I checked this scenario, scaling from 101 down to 50, and it was shifting 1 pixel to the left. Classic.
I guess to scale these ones properly, I'd have to scale from the middle of each pixel, ie add 0.5 then scale then subtrct 0.5

I've reverted this to use the getSymmetricScalingCoordinateMap() again.
But used the apply() function to ensure each pixel is scaled onto the new mask.

The applyAtSymmetryPoints() has problems because it skips some pixels during scaling, and it sets some pixels twice:
So applyAtSymmetryPoints() looks like this:
image

});
}
});
Expand Down Expand Up @@ -1004,7 +1007,8 @@ && getPrimitive(x
*/
public BooleanMask dilute(float strength, int count) {
SymmetryType symmetryType = SymmetryType.SPAWN;
return enqueue(() -> {
boolean isPerfectSym = symmetrySettings.getSymmetry(SymmetryType.SPAWN).isPerfectSymmetry();
var q = enqueue(() -> {
int size = getSize();
for (int i = 0; i < count; i++) {
long[] maskCopy = getMaskCopy();
Expand All @@ -1016,6 +1020,10 @@ public BooleanMask dilute(float strength, int count) {
mask = maskCopy;
}
});
if (!isPerfectSym) {
q.enqueue(() -> apply(this::copyPrimitiveFromReverseLookup));
}
return q;
}

/**
Expand All @@ -1026,7 +1034,8 @@ public BooleanMask dilute(float strength, int count) {
*/
public BooleanMask erode(float strength, int count) {
SymmetryType symmetryType = SymmetryType.SPAWN;
return enqueue(() -> {
boolean isPerfectSym = symmetrySettings.getSymmetry(SymmetryType.SPAWN).isPerfectSymmetry();
var q = enqueue(() -> {
int size = getSize();
for (int i = 0; i < count; i++) {
long[] maskCopy = getMaskCopy();
Expand All @@ -1038,6 +1047,10 @@ public BooleanMask erode(float strength, int count) {
mask = maskCopy;
}
});
if (!isPerfectSym) {
q.enqueue(() -> apply(this::copyPrimitiveFromReverseLookup));
}
return q;
}

public BooleanMask addBrush(Vector2 location, String brushName, float minValue, float maxValue, int size) {
Expand Down
76 changes: 69 additions & 7 deletions shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public FloatMask addPerlinNoise(int resolution, float scale) {
FloatMask noise = new FloatMask(size, null, symmetrySettings, getName() + "PerlinNoise", isParallel());
noise.enqueue(dependencies -> {
Vector2Mask source = (Vector2Mask) dependencies.get(0);
noise.setPrimitiveWithSymmetry(SymmetryType.SPAWN, (x, y) -> {
noise.setPrimitiveWithSymmetryUsingReverseLookup(SymmetryType.SPAWN, (x, y) -> {
int xLow = (int) (x / gradientScale);
float dXLow = x / gradientScale - xLow;
int xHigh = xLow + 1;
Expand Down Expand Up @@ -237,7 +237,7 @@ public FloatMask addGaussianNoise(float scale) {
* @param scale Multiplicative factor for the noise
*/
public FloatMask addWhiteNoise(float scale) {
return addPrimitiveWithSymmetry(SymmetryType.SPAWN, (x, y) -> random.nextFloat() * scale);
return addPrimitiveWithSymmetryUsingReverseLookup(SymmetryType.SPAWN, (x, y) -> random.nextFloat() * scale);
}

/**
Expand All @@ -248,7 +248,7 @@ public FloatMask addWhiteNoise(float scale) {
*/
public FloatMask addWhiteNoise(float minValue, float maxValue) {
float range = maxValue - minValue;
return addPrimitiveWithSymmetry(SymmetryType.SPAWN, (x, y) -> random.nextFloat() * range + minValue);
return addPrimitiveWithSymmetryUsingReverseLookup(SymmetryType.SPAWN, (x, y) -> random.nextFloat() * range + minValue);
}

public FloatMask waterErode(int numDrops, int maxIterations, float friction, float speed, float erosionRate, float depositionRate, float maxOffset, float iterationScale) {
Expand Down Expand Up @@ -662,10 +662,13 @@ protected FloatMask setSizeInternal(int newSize) {
} else if (oldSize != newSize) {
float[][] oldMask = mask;
initializeMask(newSize);
Map<Integer, Integer> coordinateMap = getSymmetricScalingCoordinateMap(oldSize, newSize);
applyWithSymmetry(SymmetryType.SPAWN, (x, y) -> {
float value = oldMask[coordinateMap.get(x)][coordinateMap.get(y)];
applyAtSymmetryPoints(x, y, SymmetryType.SPAWN, (sx, sy) -> setPrimitive(sx, sy, value));

float scale = (float)oldSize / (float)newSize;

apply((x, y) -> {
int sx = (int)(x * scale);
int sy = (int)(y * scale);
setPrimitive(x, y, oldMask[sx][sy]);
});
}
});
Expand Down Expand Up @@ -954,19 +957,54 @@ public FloatMask setPrimitiveWithSymmetry(SymmetryType symmetryType, ToFloatBiIn
});
}

public FloatMask setPrimitiveWithSymmetryUsingReverseLookup(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) {
boolean isPerfectSym = symmetrySettings.getSymmetry(SymmetryType.SPAWN).isPerfectSymmetry();
if (isPerfectSym) {
return setPrimitiveWithSymmetry(symmetryType, valueFunction);
} else {
return applyWithSymmetry(symmetryType, (x, y) -> {
float value = valueFunction.apply(x, y);
setPrimitive(x, y, value);
}).apply(this::copyPrimitiveFromReverseLookup);
}
}

public FloatMask addPrimitiveWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) {
return applyWithSymmetry(symmetryType, (x, y) -> {
float value = valueFunction.apply(x, y);
applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> addPrimitiveAt(sx, sy, value));
});
}

public FloatMask addPrimitiveWithSymmetryUsingReverseLookup(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) {
boolean isPerfectSym = symmetrySettings.getSymmetry(SymmetryType.SPAWN).isPerfectSymmetry();
if (isPerfectSym) {
return addPrimitiveWithSymmetry(symmetryType, valueFunction);
} else {
return applyWithSymmetry(symmetryType, (x, y) -> {
float value = valueFunction.apply(x, y);
addPrimitiveAt(x, y, value);
}).apply(this::copyPrimitiveFromReverseLookup);
}
}

public FloatMask subtractPrimitiveWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) {
return applyWithSymmetry(symmetryType, (x, y) -> {
float value = valueFunction.apply(x, y);
applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> subtractPrimitiveAt(sx, sy, value));
});
}
public FloatMask subtractPrimitiveWithSymmetryUsingReverseLookup(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) {
boolean isPerfectSym = symmetrySettings.getSymmetry(SymmetryType.SPAWN).isPerfectSymmetry();
if (isPerfectSym) {
return subtractPrimitiveWithSymmetry(symmetryType, valueFunction);
} else {
return applyWithSymmetry(symmetryType, (x, y) -> {
float value = valueFunction.apply(x, y);
subtractPrimitiveAt(x, y, value);
}).apply(this::copyPrimitiveFromReverseLookup);
}
}

public FloatMask multiplyPrimitiveWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) {
return applyWithSymmetry(symmetryType, (x, y) -> {
Expand All @@ -975,13 +1013,37 @@ public FloatMask multiplyPrimitiveWithSymmetry(SymmetryType symmetryType, ToFloa
});
}

public FloatMask multiplyPrimitiveWithSymmetryUsingReverseLookup(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) {
boolean isPerfectSym = symmetrySettings.getSymmetry(SymmetryType.SPAWN).isPerfectSymmetry();
if (isPerfectSym) {
return multiplyPrimitiveWithSymmetry(symmetryType, valueFunction);
} else {
return applyWithSymmetry(symmetryType, (x, y) -> {
float value = valueFunction.apply(x, y);
multiplyPrimitiveAt(x, y, value);
}).apply(this::copyPrimitiveFromReverseLookup);
}
}

public FloatMask dividePrimitiveWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) {
return applyWithSymmetry(symmetryType, (x, y) -> {
float value = valueFunction.apply(x, y);
applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> dividePrimitiveAt(sx, sy, value));
});
}

public FloatMask dividePrimitiveWithSymmetryUsingReverseLookup(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) {
boolean isPerfectSym = symmetrySettings.getSymmetry(SymmetryType.SPAWN).isPerfectSymmetry();
if (isPerfectSym) {
return dividePrimitiveWithSymmetry(symmetryType, valueFunction);
} else {
return applyWithSymmetry(symmetryType, (x, y) -> {
float value = valueFunction.apply(x, y);
dividePrimitiveAt(x, y, value);
}).apply(this::copyPrimitiveFromReverseLookup);
}
}

private FloatMask applyWithOffset(FloatMask other, BiIntFloatConsumer action, int xOffset, int yOffset, boolean center, boolean wrapEdges) {
return enqueue(() -> {
int size = getSize();
Expand Down
51 changes: 47 additions & 4 deletions shared/src/main/java/com/faforever/neroxis/mask/Mask.java
Original file line number Diff line number Diff line change
Expand Up @@ -617,10 +617,16 @@ public boolean inHalf(Vector3 pos, float angle) {

public U forceSymmetry(SymmetryType symmetryType, boolean reverse) {
if (!reverse) {
return applyWithSymmetry(symmetryType, (x, y) -> {
T value = get(x, y);
applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> set(sx, sy, value));
});
boolean isPerfectSym = symmetrySettings.getSymmetry(SymmetryType.SPAWN).isPerfectSymmetry();
if (!isPerfectSym) {
// When we don't have a perfect symmetry, we can skip this.
return enqueue(() -> {});
} else {
return applyWithSymmetry(symmetryType, (x, y) -> {
T value = get(x, y);
applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> set(sx, sy, value));
});
}
} else {
if (symmetrySettings.getSymmetry(symmetryType).getNumSymPoints() != 2) {
throw new IllegalArgumentException("Symmetry has more than two symmetry points");
Expand Down Expand Up @@ -682,6 +688,43 @@ public boolean inBounds(Vector2 location) {
return inBounds(StrictMath.round(location.getX()), StrictMath.round(location.getY()));
}

void copyPrimitiveFromReverseLookup(int x, int y) {
int numSpawns = symmetrySettings.spawnSymmetry().getNumSymPoints();
double radiansPerSlice = StrictMath.PI * 2 / numSpawns;
int size = getSize();
int dx = x - (size / 2);
int dy = y - (size / 2);

// Find the angle of this point relative to the center of the map
double angle = StrictMath.atan2(dy, dx);
if (y < 0) {
angle = StrictMath.PI - angle;
} else {
angle = StrictMath.PI + angle;
}

// Find out what slice of the pie this pixel sits in
int slice = (int) (angle / radiansPerSlice);
if (slice > 0) {
// Find the angle we need to rotate, in order to lookup this pixels value on the original slice.
double antiRotateAngle = -slice * radiansPerSlice;

// Find the X and Y coords of this pixel in the original slice
float halfSize = size / 2f;
float xOffset = x - halfSize;
float yOffset = y - halfSize;
double cosAngle = StrictMath.cos(antiRotateAngle);
double sinAngle = StrictMath.sin(antiRotateAngle);
float antiRotatedX = (float) (xOffset * cosAngle - yOffset * sinAngle + halfSize);
float antiRotatedY = (float) (xOffset * sinAngle + yOffset * cosAngle + halfSize);

// Copy the value from the original slice
if (inBounds((int) antiRotatedX, (int) antiRotatedY)) {
set(x, y, get((int) antiRotatedX, (int) antiRotatedY));
}
}
}

public U forceSymmetry(SymmetryType symmetryType) {
return forceSymmetry(symmetryType, false);
}
Expand Down