From 6ad6e2a0ac59a197ca0fb435e81fdbd836f25dfc Mon Sep 17 00:00:00 2001 From: clive Date: Fri, 12 Jul 2024 23:51:54 +0800 Subject: [PATCH 1/3] A new strategy to fix odd symmetry generation, it involves use a reverse lookup to find the original pixel. --- .../faforever/neroxis/mask/BooleanMask.java | 25 ++++-- .../com/faforever/neroxis/mask/FloatMask.java | 76 +++++++++++++++++-- .../java/com/faforever/neroxis/mask/Mask.java | 53 +++++++++++-- 3 files changed, 136 insertions(+), 18 deletions(-) diff --git a/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java b/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java index 4a9b6c13..8ccbc4aa 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java @@ -229,10 +229,13 @@ protected BooleanMask setSizeInternal(int newSize) { } else if (oldSize != newSize) { long[] oldMask = mask; initializeMask(newSize); - Map 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)); }); } }); @@ -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(); @@ -1016,6 +1020,10 @@ public BooleanMask dilute(float strength, int count) { mask = maskCopy; } }); + if (!isPerfectSym) { + q.enqueue(() -> apply(this::copyPrimitiveFromReverseLookup)); + } + return q; } /** @@ -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(); @@ -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) { diff --git a/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java b/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java index 13b0dd91..d66d9ffa 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java @@ -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; @@ -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); } /** @@ -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) { @@ -662,10 +662,13 @@ protected FloatMask setSizeInternal(int newSize) { } else if (oldSize != newSize) { float[][] oldMask = mask; initializeMask(newSize); - Map 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]); }); } }); @@ -954,6 +957,18 @@ 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); @@ -961,12 +976,35 @@ public FloatMask addPrimitiveWithSymmetry(SymmetryType symmetryType, ToFloatBiIn }); } + 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) -> { @@ -975,6 +1013,18 @@ 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); @@ -982,6 +1032,18 @@ public FloatMask dividePrimitiveWithSymmetry(SymmetryType symmetryType, ToFloatB }); } + 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(); diff --git a/shared/src/main/java/com/faforever/neroxis/mask/Mask.java b/shared/src/main/java/com/faforever/neroxis/mask/Mask.java index 5b6fbe1b..d9c91727 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/Mask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/Mask.java @@ -403,7 +403,7 @@ public List getSymmetryPointsWithOutOfBounds(float x, float y, Symmetry case POINT3, POINT5, POINT7, POINT9, POINT11, POINT13, POINT15 -> { for (int i = 1; i < numSymPoints; i++) { Vector2 rotated = getRotatedPoint(x, y, (float) (2 * StrictMath.PI * i / numSymPoints)); - symmetryPoints.add(rotated); + symmetryPoints.add(rotated); } } case X -> symmetryPoints.add(new Vector2(size - x - 1, y)); @@ -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"); @@ -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); } From 2c7af38b4d5a636ce8c8cb09f8542dc3c50a1c6d Mon Sep 17 00:00:00 2001 From: clive Date: Fri, 12 Jul 2024 23:54:34 +0800 Subject: [PATCH 2/3] cleanup --- shared/src/main/java/com/faforever/neroxis/mask/Mask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/main/java/com/faforever/neroxis/mask/Mask.java b/shared/src/main/java/com/faforever/neroxis/mask/Mask.java index d9c91727..491ccd2e 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/Mask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/Mask.java @@ -403,7 +403,7 @@ public List getSymmetryPointsWithOutOfBounds(float x, float y, Symmetry case POINT3, POINT5, POINT7, POINT9, POINT11, POINT13, POINT15 -> { for (int i = 1; i < numSymPoints; i++) { Vector2 rotated = getRotatedPoint(x, y, (float) (2 * StrictMath.PI * i / numSymPoints)); - symmetryPoints.add(rotated); + symmetryPoints.add(rotated); } } case X -> symmetryPoints.add(new Vector2(size - x - 1, y)); From d67c6599c49d49853bfbdb447e7fa4b0feb2312a Mon Sep 17 00:00:00 2001 From: clive Date: Sat, 13 Jul 2024 10:44:40 +0800 Subject: [PATCH 3/3] Changed the setSizeInternal() function to use the coordinateMap again, but use apply() instead of applyWithSymmetry(). --- .../java/com/faforever/neroxis/mask/BooleanMask.java | 9 +++------ .../main/java/com/faforever/neroxis/mask/FloatMask.java | 9 +++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java b/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java index 8ccbc4aa..61c88909 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java @@ -229,13 +229,10 @@ protected BooleanMask setSizeInternal(int newSize) { } else if (oldSize != newSize) { long[] oldMask = mask; initializeMask(newSize); - - float scale = (float)oldSize / (float)newSize; - + Map coordinateMap = getSymmetricScalingCoordinateMap(oldSize, newSize); apply((x, y) -> { - int sx = (int)(x * scale); - int sy = (int)(y * scale); - setPrimitive(x, y, getBit(sx, sy, oldSize, oldMask)); + boolean value = getBit(coordinateMap.get(x), coordinateMap.get(y), oldSize, oldMask); + setPrimitive(x, y, value); }); } }); diff --git a/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java b/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java index d66d9ffa..0df3e024 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java @@ -662,13 +662,10 @@ protected FloatMask setSizeInternal(int newSize) { } else if (oldSize != newSize) { float[][] oldMask = mask; initializeMask(newSize); - - float scale = (float)oldSize / (float)newSize; - + Map coordinateMap = getSymmetricScalingCoordinateMap(oldSize, newSize); apply((x, y) -> { - int sx = (int)(x * scale); - int sy = (int)(y * scale); - setPrimitive(x, y, oldMask[sx][sy]); + float value = oldMask[coordinateMap.get(x)][coordinateMap.get(y)]; + setPrimitive(x, y, value); }); } });