From 71a9ac1d6ab2681eb163053431a4a918bad7cfe5 Mon Sep 17 00:00:00 2001 From: Sheikah45 Date: Sun, 14 Jul 2024 13:56:48 -0400 Subject: [PATCH 1/8] Move symmetry bound functions to dedicated util --- .../faforever/neroxis/mask/BooleanMask.java | 52 ++++-- .../com/faforever/neroxis/mask/FloatMask.java | 33 ++-- .../faforever/neroxis/mask/IntegerMask.java | 2 +- .../java/com/faforever/neroxis/mask/Mask.java | 93 +++------- .../faforever/neroxis/mask/VectorMask.java | 2 +- .../com/faforever/neroxis/util/MathUtil.java | 4 + .../faforever/neroxis/util/SymmetryUtil.java | 78 +++++++++ .../neroxis/util/SymmetryUtilTest.java | 159 ++++++++++++++++++ 8 files changed, 314 insertions(+), 109 deletions(-) create mode 100644 shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java create mode 100644 shared/src/test/java/com/faforever/neroxis/util/SymmetryUtilTest.java 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 666180fa2..cf1d6a77c 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java @@ -20,7 +20,10 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; +import java.util.function.IntUnaryOperator; import java.util.stream.Collectors; +import java.util.stream.IntStream; import static com.faforever.neroxis.brushes.Brushes.loadBrush; @@ -183,7 +186,7 @@ public BufferedImage toImage() { public String toHash() throws NoSuchAlgorithmException { int size = getSize(); ByteBuffer bytes = ByteBuffer.allocate(size * size); - loopWithSymmetry(SymmetryType.SPAWN, (x, y) -> bytes.put(getPrimitive(x, y) ? (byte) 1 : 0)); + loopInSymmetryRegion(SymmetryType.SPAWN, (x, y) -> bytes.put(getPrimitive(x, y) ? (byte) 1 : 0)); byte[] data = MessageDigest.getInstance("MD5").digest(bytes.array()); StringBuilder stringBuilder = new StringBuilder(); for (byte datum : data) { @@ -597,12 +600,14 @@ public , U extends ComparableMask> BooleanMask ini public BooleanMask randomWalk(int numWalkers, int numSteps) { return enqueue(() -> { int size = getSize(); + int maxXBound = getMaxXBound(SymmetryType.TERRAIN); + int minXBound = 0; + IntUnaryOperator maxYBoundFunction = getMaxYBoundFunction(SymmetryType.TERRAIN); + IntUnaryOperator minYBoundFunction = getMinYBoundFunction(SymmetryType.TERRAIN); for (int i = 0; i < numWalkers; i++) { - int maxXBound = getMaxXBound(SymmetryType.TERRAIN); - int minXBound = getMinXBound(SymmetryType.TERRAIN); int x = random.nextInt(maxXBound - minXBound) + minXBound; - int maxYBound = getMaxYBound(x, SymmetryType.TERRAIN); - int minYBound = getMinYBound(x, SymmetryType.TERRAIN); + int maxYBound = maxYBoundFunction.applyAsInt(x); + int minYBound = minYBoundFunction.applyAsInt(x); int y = random.nextInt(maxYBound - minYBound + 1) + minYBound; for (int j = 0; j < numSteps; j++) { if (inBounds(x, y, size)) { @@ -713,9 +718,8 @@ public BooleanMask path(Vector2 start, Vector2 end, float maxStepSize, int numMi float angle = (float) ((random.nextFloat() - .5f) * 2 * StrictMath.PI / 2f) + previousLoc.angleTo(end); if (symmetrySettings.terrainSymmetry() == Symmetry.POINT4 && angle % (StrictMath.PI / 2) < StrictMath.PI / 8) { - angle += (float) ( - (random.nextBoolean() ? -1 : 1) * (random.nextFloat() * .5f + .5f) * 2f * StrictMath.PI - / 4f); + int direction = random.nextBoolean() ? -1 : 1; + angle += (float) (direction * (random.nextFloat() * .5f + .5f) * 2f * StrictMath.PI / 4f); } float magnitude = random.nextFloat() * (midPointMaxDistance - midPointMinDistance) + midPointMinDistance; @@ -820,11 +824,15 @@ public BooleanMask deflate(float radius) { */ public BooleanMask progressiveWalk(int numWalkers, int numSteps) { int size = getSize(); + IntUnaryOperator maxYBoundFunction = getMaxYBoundFunction(SymmetryType.TERRAIN); + IntUnaryOperator minYBoundFunction = getMinYBoundFunction(SymmetryType.TERRAIN); + int maxXBound = getMaxXBound(SymmetryType.TERRAIN); + int minXBound = 0; for (int i = 0; i < numWalkers; i++) { - int x = random.nextInt(getMaxXBound(SymmetryType.TERRAIN) - getMinXBound(SymmetryType.TERRAIN)) - + getMinXBound(SymmetryType.TERRAIN); - int y = random.nextInt(getMaxYBound(x, SymmetryType.TERRAIN) - getMinYBound(x, SymmetryType.TERRAIN) + 1) - + getMinYBound(x, SymmetryType.TERRAIN); + int x = random.nextInt(maxXBound - minXBound) + minXBound; + int maxYBound = maxYBoundFunction.applyAsInt(x); + int minYBound = minYBoundFunction.applyAsInt(x); + int y = random.nextInt(maxYBound - minYBound + 1) + minYBound; List directions = new ArrayList<>(Arrays.asList(0, 1, 2, 3)); int regressiveDir = random.nextInt(directions.size()); directions.remove(regressiveDir); @@ -1091,13 +1099,21 @@ public BooleanMask limitToSymmetryRegion() { } public BooleanMask limitToSymmetryRegion(SymmetryType symmetryType) { - int minXBound = getMinXBound(symmetryType); + int minXBound = 0; int maxXBound = getMaxXBound(symmetryType); - return apply((x, y) -> { - setPrimitive(x, y, - getPrimitive(x, y) && !(x < minXBound || x >= maxXBound || y < getMinYBound(x, symmetryType) - || y >= getMaxYBound(x, symmetryType))); - }); + IntUnaryOperator minYBoundFunction = getMinYBoundFunction(symmetryType); + IntUnaryOperator maxYBoundFunction = getMaxYBoundFunction(symmetryType); + Map minYBoundMap = IntStream.range(minXBound, maxXBound) + .boxed() + .collect(Collectors.toMap(Function.identity(), + minYBoundFunction::applyAsInt)); + Map maxYBoundMap = IntStream.range(minXBound, maxXBound) + .boxed() + .collect(Collectors.toMap(Function.identity(), + maxYBoundFunction::applyAsInt)); + return apply((x, y) -> setPrimitive(x, y, getPrimitive(x, y) && !(x < minXBound || x >= maxXBound + || y < minYBoundMap.get(x) + || y >= maxYBoundMap.get(x)))); } /** 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 357e32f5f..a0c52ee42 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java @@ -148,10 +148,9 @@ public FloatMask addPerlinNoise(int resolution, float scale) { int size = getSize(); int gradientSize = size / resolution; float gradientScale = (float) size / gradientSize; - Vector2Mask gradientVectors = new Vector2Mask(gradientSize + - 1, random.nextLong(), new SymmetrySettings(Symmetry.NONE), - getName() + - "PerlinVectors", isParallel()); + Vector2Mask gradientVectors = new Vector2Mask(gradientSize + 1, random.nextLong(), + new SymmetrySettings(Symmetry.NONE), getName() + "PerlinVectors", + isParallel()); gradientVectors.randomize(-1f, 1f).normalize(); FloatMask noise = new FloatMask(size, null, symmetrySettings, getName() + "PerlinNoise", isParallel()); noise.enqueue(dependencies -> { @@ -307,8 +306,8 @@ private void waterDrop(int maxIterations, float x, float y, float friction, floa public FloatMask removeAreasOfSpecifiedSizeWithLocalMaximums(int minSize, int maxSize, int levelOfPrecision, float floatMax) { for (int x = 0; x < levelOfPrecision; x++) { - removeAreasInIntensityAndSize(minSize, maxSize, ((1f - (float) x / (float) levelOfPrecision) * - floatMax), floatMax); + removeAreasInIntensityAndSize(minSize, maxSize, ((1f - (float) x / (float) levelOfPrecision) * floatMax), + floatMax); } removeAreasInIntensityAndSize(minSize, maxSize, 0.0000001f, floatMax); return this; @@ -395,9 +394,8 @@ public FloatMask useBrushWithinAreaWithDensity(BooleanMask other, String brushNa float intensity, boolean wrapEdges) { enqueue(dependencies -> { BooleanMask source = (BooleanMask) dependencies.getFirst(); - int frequency = (int) (density * (float) source.getCount() / - 26.21f / - symmetrySettings.spawnSymmetry().getNumSymPoints()); + int frequency = (int) (density * (float) source.getCount() / 26.21f / symmetrySettings.spawnSymmetry() + .getNumSymPoints()); useBrushWithinArea(source, brushName, size, frequency, intensity, wrapEdges); }, other); return this; @@ -423,8 +421,7 @@ public BooleanMask copyAsShadowMask(Vector3 lightDirection) { float angle = (float) ((lightDirection.getAzimuth() - StrictMath.PI) % (StrictMath.PI * 2)); float slope = (float) StrictMath.tan(lightDirection.getElevation()); BooleanMask shadowMask = new BooleanMask(getSize(), getNextSeed(), new SymmetrySettings(Symmetry.NONE), - getName() + - "Shadow", isParallel()); + getName() + "Shadow", isParallel()); return shadowMask.enqueue(dependencies -> shadowMask.apply((x, y) -> { FloatMask source = (FloatMask) dependencies.getFirst(); Vector2 location = new Vector2(x, y); @@ -515,15 +512,15 @@ private void addCalculatedParabolicDistance(boolean useColumns) { } Vector2 current = new Vector2(j, value); Vector2 vertex = vertices.get(index); - float xIntersect = ((current.getY() + current.getX() * current.getX()) - - (vertex.getY() + vertex.getX() * vertex.getX())) / - (2 * current.getX() - 2 * vertex.getX()); + float xIntersect = ((current.getY() + current.getX() * current.getX()) - (vertex.getY() + vertex.getX() + * vertex.getX())) + / (2 * current.getX() - 2 * vertex.getX()); while (xIntersect <= intersections.get(index).getX()) { index -= 1; vertex = vertices.get(index); - xIntersect = ((current.getY() + current.getX() * current.getX()) - - (vertex.getY() + vertex.getX() * vertex.getX())) / - (2 * current.getX() - 2 * vertex.getX()); + xIntersect = ((current.getY() + current.getX() * current.getX()) - (vertex.getY() + vertex.getX() + * vertex.getX())) + / (2 * current.getX() - 2 * vertex.getX()); } index += 1; if (index < vertices.size()) { @@ -619,7 +616,7 @@ public BufferedImage toImage() { public String toHash() throws NoSuchAlgorithmException { int size = getSize(); ByteBuffer bytes = ByteBuffer.allocate(size * size * 4); - loopWithSymmetry(SymmetryType.SPAWN, (x, y) -> bytes.putFloat(getPrimitive(x, y))); + loopInSymmetryRegion(SymmetryType.SPAWN, (x, y) -> bytes.putFloat(getPrimitive(x, y))); byte[] data = MessageDigest.getInstance("MD5").digest(bytes.array()); StringBuilder stringBuilder = new StringBuilder(); for (byte datum : data) { diff --git a/shared/src/main/java/com/faforever/neroxis/mask/IntegerMask.java b/shared/src/main/java/com/faforever/neroxis/mask/IntegerMask.java index 8ff376e58..bf960d5ac 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/IntegerMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/IntegerMask.java @@ -159,7 +159,7 @@ public BufferedImage toImage() { @Override public String toHash() throws NoSuchAlgorithmException { ByteBuffer bytes = ByteBuffer.allocate(getSize() * getSize() * 4); - loopWithSymmetry(SymmetryType.SPAWN, (x, y) -> bytes.putInt(getPrimitive(x, y))); + loopInSymmetryRegion(SymmetryType.SPAWN, (x, y) -> bytes.putInt(getPrimitive(x, y))); byte[] data = MessageDigest.getInstance("MD5").digest(bytes.array()); StringBuilder stringBuilder = new StringBuilder(); for (byte datum : data) { 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 cb96d6207..1dad34d17 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/Mask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/Mask.java @@ -5,6 +5,7 @@ import com.faforever.neroxis.map.SymmetryType; import com.faforever.neroxis.util.DebugUtil; import com.faforever.neroxis.util.Pipeline; +import com.faforever.neroxis.util.SymmetryUtil; import com.faforever.neroxis.util.functional.BiIntConsumer; import com.faforever.neroxis.util.functional.BiIntFunction; import com.faforever.neroxis.util.functional.BiIntObjConsumer; @@ -24,6 +25,7 @@ import java.util.Map; import java.util.Random; import java.util.function.Consumer; +import java.util.function.IntUnaryOperator; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -288,35 +290,25 @@ protected static boolean inBounds(int x, int y, int size) { } public boolean inTeam(int x, int y, boolean reverse) { - return (x >= getMinXBound(SymmetryType.TEAM) && + return (x >= 0 && x < getMaxXBound(SymmetryType.TEAM) && - y >= getMinYBound(x, SymmetryType.TEAM) && - y < getMaxYBound(x, SymmetryType.TEAM)) ^ reverse && inBounds(x, y); + y >= getMinYBoundFunction(SymmetryType.TEAM).applyAsInt(x) && + y < getMaxYBoundFunction(SymmetryType.TEAM).applyAsInt(x)) ^ reverse && inBounds(x, y); } - protected int getMinXBound(SymmetryType symmetryType) { - return 0; + protected int getMaxXBound(SymmetryType symmetryType) { + Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); + return SymmetryUtil.getMaxXBound(symmetry, getSize()); } - protected int getMaxXBound(SymmetryType symmetryType) { + protected IntUnaryOperator getMinYBoundFunction(SymmetryType symmetryType) { Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); - int size = getSize(); - return switch (symmetry) { - case POINT3, POINT5, POINT6, POINT7, POINT8, POINT9, POINT10, POINT11, POINT12, POINT13, POINT14, POINT15, - POINT16 -> StrictMath.max(getMaxXFromAngle(360f / symmetry.getNumSymPoints()), size / 2 + 1); - case POINT4, X, QUAD, DIAG -> size / 2; - case POINT2, XZ, ZX, Z, NONE -> size; - }; + return SymmetryUtil.getMinYBoundFunction(symmetry, getSize()); } - protected int getMinYBound(int x, SymmetryType symmetryType) { + protected IntUnaryOperator getMaxYBoundFunction(SymmetryType symmetryType) { Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); - return switch (symmetry) { - case POINT2, POINT3, POINT4, POINT5, POINT6, POINT7, POINT8, POINT9, POINT10, POINT11, POINT12, POINT13, - POINT14, POINT15, POINT16 -> getMinYFromXOnArc(x, 360f / symmetry.getNumSymPoints()); - case DIAG, XZ -> x; - case ZX, X, Z, QUAD, NONE -> 0; - }; + return SymmetryUtil.getMaxYBoundFunction(symmetry, getSize()); } /** @@ -359,18 +351,6 @@ public U setToValue(BooleanMask area, U value) { }, area, value); } - protected int getMaxYBound(int x, SymmetryType symmetryType) { - Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); - final int size = getSize(); - return switch (symmetry) { - case POINT3, POINT5, POINT6, POINT7, POINT8, POINT9, POINT10, POINT11, POINT12, POINT13, POINT14, POINT15, - POINT16 -> getMaxYFromXOnArc(x, 360f / symmetry.getNumSymPoints()); - case ZX, DIAG -> size - x; - case Z, POINT2, POINT4, QUAD -> size / 2 + size % 2; - case X, NONE, XZ -> size; - }; - } - public boolean inBounds(int x, int y) { int size = getSize(); return inBounds(x, y, getSize()); @@ -543,40 +523,10 @@ public U resample(int newSize) { } public boolean inTeamNoBounds(int x, int y, boolean reverse) { - return (x >= getMinXBound(SymmetryType.TEAM) && + return (x >= 0 && x < getMaxXBound(SymmetryType.TEAM) && - y >= getMinYBound(x, SymmetryType.TEAM) && - y < getMaxYBound(x, SymmetryType.TEAM)) ^ reverse; - } - - private int getMaxXFromAngle(float angle) { - int size = getSize(); - int x = (int) StrictMath.round(StrictMath.cos(((angle + 180) / 180) % 2 * StrictMath.PI) * size + size / 2f); - return StrictMath.max(StrictMath.min(x, size), 0); - } - - private int getMinYFromXOnArc(int x, float angle) { - int size = getSize(); - float dx = x - size / 2f; - int y; - if (x > getMaxXFromAngle(angle)) { - y = (int) (size / 2f + StrictMath.tan(((angle + 180) / 180) % 2 * StrictMath.PI) * dx); - } else { - y = (int) StrictMath.round(size / 2f - StrictMath.sqrt(size * size - dx * dx)); - } - return StrictMath.max(StrictMath.min(y, size), 0); - } - - private int getMaxYFromXOnArc(int x, float angle) { - int size = getSize(); - float dx = x - size / 2f; - int y; - if (x > size / 2) { - y = (int) (size / 2f + StrictMath.tan(((angle + 180) / 180) % 2 * StrictMath.PI) * dx); - } else { - y = size / 2 + 1; - } - return StrictMath.max(StrictMath.min(y, getSize()), 0); + y >= getMinYBoundFunction(SymmetryType.TEAM).applyAsInt(x) && + y < getMaxYBoundFunction(SymmetryType.TEAM).applyAsInt(x)) ^ reverse; } public boolean inTeam(Vector3 pos, boolean reverse) { @@ -643,7 +593,7 @@ public boolean inHalfNoBounds(Vector3 pos, float angle) { protected U applyWithSymmetry(SymmetryType symmetryType, BiIntConsumer maskAction) { return enqueue(() -> { - loopWithSymmetry(symmetryType, maskAction); + loopInSymmetryRegion(symmetryType, maskAction); if (!symmetrySettings.getSymmetry(symmetryType).isPerfectSymmetry() && symmetrySettings.spawnSymmetry().isPerfectSymmetry()) { forceSymmetry(SymmetryType.SPAWN); @@ -810,13 +760,14 @@ protected Map getShiftedCoordinateMap(int offset, boolean cent .collect(Collectors.toMap(i -> i, i -> getShiftedValue(i, trueOffset, toSize, wrapEdges))); } - protected void loopWithSymmetry(SymmetryType symmetryType, BiIntConsumer maskAction) { + protected void loopInSymmetryRegion(SymmetryType symmetryType, BiIntConsumer maskAction) { assertNotPipelined(); - int minX = getMinXBound(symmetryType); int maxX = getMaxXBound(symmetryType); - for (int x = minX; x < maxX; x++) { - int minY = getMinYBound(x, symmetryType); - int maxY = getMaxYBound(x, symmetryType); + IntUnaryOperator minYBoundFunction = getMinYBoundFunction(symmetryType); + IntUnaryOperator maxYBoundFunction = getMaxYBoundFunction(symmetryType); + for (int x = 0; x < maxX; x++) { + int minY = minYBoundFunction.applyAsInt(x); + int maxY = maxYBoundFunction.applyAsInt(x); for (int y = minY; y < maxY; y++) { maskAction.accept(x, y); } diff --git a/shared/src/main/java/com/faforever/neroxis/mask/VectorMask.java b/shared/src/main/java/com/faforever/neroxis/mask/VectorMask.java index bdd5e8301..b00249dfd 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/VectorMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/VectorMask.java @@ -122,7 +122,7 @@ public String toHash() throws NoSuchAlgorithmException { int size = getSize(); int dimension = get(0, 0).getDimension(); ByteBuffer bytes = ByteBuffer.allocate(size * size * 4 * dimension); - loopWithSymmetry(SymmetryType.SPAWN, (x, y) -> { + loopInSymmetryRegion(SymmetryType.SPAWN, (x, y) -> { Vector value = get(x, y); for (int i = 0; i < dimension; ++i) { bytes.putFloat(value.get(i)); diff --git a/shared/src/main/java/com/faforever/neroxis/util/MathUtil.java b/shared/src/main/java/com/faforever/neroxis/util/MathUtil.java index c420c73f2..5c4a9f4fb 100644 --- a/shared/src/main/java/com/faforever/neroxis/util/MathUtil.java +++ b/shared/src/main/java/com/faforever/neroxis/util/MathUtil.java @@ -36,4 +36,8 @@ public static int binomialCoefficient(int n, int k) { public static float log2(float val) { return (float) (StrictMath.log(val) / StrictMath.log(2)); } + + public static int clamp(int value, int min, int max) { + return StrictMath.max(StrictMath.min(value, max), min); + } } diff --git a/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java b/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java new file mode 100644 index 000000000..5366a1b30 --- /dev/null +++ b/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java @@ -0,0 +1,78 @@ +package com.faforever.neroxis.util; + +import com.faforever.neroxis.map.Symmetry; + +import java.util.function.IntUnaryOperator; + +public class SymmetryUtil { + + public static int getMaxXBound(Symmetry symmetry, int size) { + return switch (symmetry) { + case POINT4, POINT5, POINT6, POINT7, POINT8, POINT9, POINT10, POINT11, POINT12, POINT13, POINT14, POINT15, + POINT16, X, QUAD, DIAG -> size / 2 + size % 2; + case POINT2, POINT3, XZ, ZX, Z, NONE -> size; + }; + } + + public static IntUnaryOperator getMinYBoundFunction(Symmetry symmetry, int size) { + return switch (symmetry) { + case POINT5, POINT6, POINT7, POINT8, POINT9, POINT10, POINT11, POINT12, POINT13, POINT14, POINT15, + POINT16 -> { + double radians = convertToRotatedRadians(360f / symmetry.getNumSymPoints()); + double tan = StrictMath.tan(radians); + int halfSizeBound = size / 2 + size % 2; + float halfSize = size / 2f; + yield x -> { + if (x > halfSizeBound) { + return 0; + } + + float dx = x - halfSize; + int y = (int) (halfSize + tan * dx); + return MathUtil.clamp(y, 0, size); + }; + } + case DIAG, XZ -> x -> x; + case POINT2, POINT3, POINT4, ZX, X, Z, QUAD, NONE -> x -> 0; + }; + } + + + public static IntUnaryOperator getMaxYBoundFunction(Symmetry symmetry, int size) { + return switch (symmetry) { + case POINT3 -> { + double tan = StrictMath.tan(convertToRotatedRadians(360f / symmetry.getNumSymPoints())); + int halfSizeBound = size / 2 + size % 2; + float halfSize = size / 2f; + yield x -> { + //The max Y in the first quadrant is always the halfway point + if (x <= halfSizeBound) { + return halfSizeBound; + } + + float dx = x - halfSize; + int y = (int) (halfSize + tan * dx); + return MathUtil.clamp(y, 0, halfSizeBound); + }; + } + case POINT5, POINT6, POINT7, POINT8, POINT9, POINT10, POINT11, POINT12, POINT13, POINT14, POINT15, + POINT16 -> { + int halfSizeBound = size / 2 + size % 2; + yield x -> x <= halfSizeBound ? halfSizeBound : 0; + } + case ZX, DIAG -> x -> size - x; + case Z, POINT2, POINT4, QUAD -> { + int maxY = size / 2 + size % 2; + yield x -> maxY; + } + case X, NONE, XZ -> x -> size; + }; + } + + static double convertToRotatedRadians(float angle) { + // For the map generator the 0 angle points to the left, so we have to adjust all angles by 180 degrees + float adjustedAngle = angle + 180; + return (adjustedAngle / 180) * StrictMath.PI; + } + +} diff --git a/shared/src/test/java/com/faforever/neroxis/util/SymmetryUtilTest.java b/shared/src/test/java/com/faforever/neroxis/util/SymmetryUtilTest.java new file mode 100644 index 000000000..1a31e61a8 --- /dev/null +++ b/shared/src/test/java/com/faforever/neroxis/util/SymmetryUtilTest.java @@ -0,0 +1,159 @@ +package com.faforever.neroxis.util; + +import com.faforever.neroxis.map.Symmetry; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Execution(ExecutionMode.CONCURRENT) +public class SymmetryUtilTest { + + @Test + public void testMaxXBound() { + int size = 256; + int halfSize = size / 2; + assertEquals(size, SymmetryUtil.getMaxXBound(Symmetry.NONE, size)); + assertEquals(size, SymmetryUtil.getMaxXBound(Symmetry.POINT2, size)); + assertEquals(size, SymmetryUtil.getMaxXBound(Symmetry.XZ, size)); + assertEquals(size, SymmetryUtil.getMaxXBound(Symmetry.ZX, size)); + assertEquals(size, SymmetryUtil.getMaxXBound(Symmetry.Z, size)); + assertEquals(size, SymmetryUtil.getMaxXBound(Symmetry.POINT3, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT4, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT5, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT6, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT7, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT9, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT10, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT8, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT11, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT12, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT13, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT14, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT15, size)); + assertEquals(halfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT16, size)); + int oddSize = size + 1; + int oddHalfSize = halfSize + 1; + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.X, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.QUAD, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.DIAG, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT4, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT5, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT6, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT7, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT8, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT9, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT10, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT11, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT12, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT13, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT14, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT15, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.POINT16, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.X, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.QUAD, oddSize)); + assertEquals(oddHalfSize, SymmetryUtil.getMaxXBound(Symmetry.DIAG, oddSize)); + } + + @Test + public void testMinYBoundFunction() { + int size = 256; + int halfSize = size / 2; + int testPoint = halfSize + 1; + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.NONE, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT2, size).applyAsInt(testPoint)); + assertEquals(testPoint, SymmetryUtil.getMinYBoundFunction(Symmetry.XZ, size).applyAsInt(testPoint)); + assertEquals(testPoint, SymmetryUtil.getMinYBoundFunction(Symmetry.DIAG, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.ZX, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.Z, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT3, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT4, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT5, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT6, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT7, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT8, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT9, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT10, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT11, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT12, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT13, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT14, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT15, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMinYBoundFunction(Symmetry.POINT16, size).applyAsInt(testPoint)); + + int dx = 10; + testPoint = halfSize - dx; + assertEquals((int) (halfSize - StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 5)) * dx), + SymmetryUtil.getMinYBoundFunction(Symmetry.POINT5, size).applyAsInt(testPoint)); + assertEquals((int) (halfSize - StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 6)) * dx), + SymmetryUtil.getMinYBoundFunction(Symmetry.POINT6, size).applyAsInt(testPoint)); + assertEquals((int) (halfSize - StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 7)) * dx), + SymmetryUtil.getMinYBoundFunction(Symmetry.POINT7, size).applyAsInt(testPoint)); + assertEquals((int) (halfSize - StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 8)) * dx), + SymmetryUtil.getMinYBoundFunction(Symmetry.POINT8, size).applyAsInt(testPoint)); + assertEquals((int) (halfSize - StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 9)) * dx), + SymmetryUtil.getMinYBoundFunction(Symmetry.POINT9, size).applyAsInt(testPoint)); + assertEquals((int) (halfSize - StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 10)) * dx), + SymmetryUtil.getMinYBoundFunction(Symmetry.POINT10, size).applyAsInt(testPoint)); + assertEquals((int) (halfSize - StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 11)) * dx), + SymmetryUtil.getMinYBoundFunction(Symmetry.POINT11, size).applyAsInt(testPoint)); + assertEquals((int) (halfSize - StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 12)) * dx), + SymmetryUtil.getMinYBoundFunction(Symmetry.POINT12, size).applyAsInt(testPoint)); + assertEquals((int) (halfSize - StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 13)) * dx), + SymmetryUtil.getMinYBoundFunction(Symmetry.POINT13, size).applyAsInt(testPoint)); + assertEquals((int) (halfSize - StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 14)) * dx), + SymmetryUtil.getMinYBoundFunction(Symmetry.POINT14, size).applyAsInt(testPoint)); + assertEquals((int) (halfSize - StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 15)) * dx), + SymmetryUtil.getMinYBoundFunction(Symmetry.POINT15, size).applyAsInt(testPoint)); + assertEquals((int) (halfSize - StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 16)) * dx), + SymmetryUtil.getMinYBoundFunction(Symmetry.POINT16, size).applyAsInt(testPoint)); + } + + @Test + public void testMaxYBoundFunction() { + int size = 256; + int halfSize = size / 2; + int testPoint = halfSize; + assertEquals(size, SymmetryUtil.getMaxYBoundFunction(Symmetry.NONE, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT2, size).applyAsInt(testPoint)); + assertEquals(size, SymmetryUtil.getMaxYBoundFunction(Symmetry.XZ, size).applyAsInt(testPoint)); + assertEquals(size, SymmetryUtil.getMaxYBoundFunction(Symmetry.X, size).applyAsInt(testPoint)); + assertEquals(size - testPoint, SymmetryUtil.getMaxYBoundFunction(Symmetry.DIAG, size).applyAsInt(testPoint)); + assertEquals(size - testPoint, SymmetryUtil.getMaxYBoundFunction(Symmetry.ZX, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.Z, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.QUAD, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT4, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT3, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT5, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT6, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT7, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT8, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT9, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT10, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT11, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT12, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT13, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT14, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT15, size).applyAsInt(testPoint)); + assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT16, size).applyAsInt(testPoint)); + + int dx = 10; + testPoint = halfSize + dx; + assertEquals((int) (halfSize + StrictMath.tan(SymmetryUtil.convertToRotatedRadians(360f / 3)) * dx), + SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT3, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT5, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT6, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT7, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT8, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT9, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT10, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT11, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT12, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT13, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT14, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT15, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT16, size).applyAsInt(testPoint)); + } + +} From 252a2516cc8cc3e737b688743798a51120270086 Mon Sep 17 00:00:00 2001 From: Sheikah45 Date: Mon, 15 Jul 2024 19:02:12 -0400 Subject: [PATCH 2/8] WIP --- .../faforever/neroxis/mask/BooleanMask.java | 165 +++++----- .../com/faforever/neroxis/mask/FloatMask.java | 51 +-- .../faforever/neroxis/mask/IntegerMask.java | 106 +++--- .../java/com/faforever/neroxis/mask/Mask.java | 307 +++++++++--------- .../neroxis/mask/OperationsMask.java | 20 +- .../faforever/neroxis/mask/VectorMask.java | 147 ++++----- .../faforever/neroxis/util/SymmetryUtil.java | 178 +++++++++- 7 files changed, 504 insertions(+), 470 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 cf1d6a77c..a19f62e7f 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java @@ -4,6 +4,7 @@ import com.faforever.neroxis.map.SymmetrySettings; import com.faforever.neroxis.map.SymmetryType; import com.faforever.neroxis.util.BezierCurve; +import com.faforever.neroxis.util.SymmetryUtil; import com.faforever.neroxis.util.functional.BiIntBooleanConsumer; import com.faforever.neroxis.util.functional.ToBooleanBiIntFunction; import com.faforever.neroxis.util.vector.Vector2; @@ -235,7 +236,7 @@ protected BooleanMask setSizeInternal(int 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)); + setPrimitive(x, y, value); }); } }); @@ -600,10 +601,11 @@ public , U extends ComparableMask> BooleanMask ini public BooleanMask randomWalk(int numWalkers, int numSteps) { return enqueue(() -> { int size = getSize(); - int maxXBound = getMaxXBound(SymmetryType.TERRAIN); + Symmetry symmetry = symmetrySettings.getSymmetry(SymmetryType.TERRAIN); int minXBound = 0; - IntUnaryOperator maxYBoundFunction = getMaxYBoundFunction(SymmetryType.TERRAIN); - IntUnaryOperator minYBoundFunction = getMinYBoundFunction(SymmetryType.TERRAIN); + int maxXBound = SymmetryUtil.getMaxXBound(symmetry, size); + IntUnaryOperator minYBoundFunction = SymmetryUtil.getMinYBoundFunction(symmetry, size); + IntUnaryOperator maxYBoundFunction = SymmetryUtil.getMaxYBoundFunction(symmetry, size); for (int i = 0; i < numWalkers; i++) { int x = random.nextInt(maxXBound - minXBound) + minXBound; int maxYBound = maxYBoundFunction.applyAsInt(x); @@ -611,7 +613,7 @@ public BooleanMask randomWalk(int numWalkers, int numSteps) { int y = random.nextInt(maxYBound - minYBound + 1) + minYBound; for (int j = 0; j < numSteps; j++) { if (inBounds(x, y, size)) { - applyAtSymmetryPoints(x, y, SymmetryType.TERRAIN, (sx, sy) -> setPrimitive(sx, sy, true)); + setPrimitive(x, y, true); } switch (random.nextInt(4)) { case 0 -> x++; @@ -621,6 +623,7 @@ public BooleanMask randomWalk(int numWalkers, int numSteps) { } } } + applySymmetry(SymmetryType.TERRAIN); }); } @@ -687,7 +690,7 @@ public BooleanMask pathBezier(Vector2 start, Vector2 end, int minOrder, int maxO for (float j = 0; j <= 1; j += 1f / size) { points.add(bezierCurve.getPoint(j)); } - fillCoordinates(points.stream().filter(this::inBounds).collect(Collectors.toList()), true); + fillCoordinates(points.stream().filter(this::inBounds).toList(), true); } return this; } @@ -736,8 +739,7 @@ public BooleanMask path(Vector2 start, Vector2 end, float maxStepSize, int numMi while (location.getDistance(nextLoc) > maxStepSize && numSteps < size * size) { List symmetryPoints = getSymmetryPoints(location, symmetryType); if (inBounds(location) && symmetryPoints.stream().allMatch(this::inBounds)) { - applyAtSymmetryPoints((int) location.getX(), (int) location.getY(), SymmetryType.TERRAIN, - (sx, sy) -> setPrimitive(sx, sy, true)); + setPrimitive((int) location.getX(), (int) location.getY(), true); } float magnitude = StrictMath.max(1, random.nextFloat() * maxStepSize); float angle = oldAngle * .5f + location.angleTo(nextLoc) * .5f @@ -750,6 +752,7 @@ public BooleanMask path(Vector2 start, Vector2 end, float maxStepSize, int numMi break; } } + applySymmetry(SymmetryType.TERRAIN); }); } @@ -823,32 +826,36 @@ public BooleanMask deflate(float radius) { * @return the modified mask */ public BooleanMask progressiveWalk(int numWalkers, int numSteps) { - int size = getSize(); - IntUnaryOperator maxYBoundFunction = getMaxYBoundFunction(SymmetryType.TERRAIN); - IntUnaryOperator minYBoundFunction = getMinYBoundFunction(SymmetryType.TERRAIN); - int maxXBound = getMaxXBound(SymmetryType.TERRAIN); - int minXBound = 0; - for (int i = 0; i < numWalkers; i++) { - int x = random.nextInt(maxXBound - minXBound) + minXBound; - int maxYBound = maxYBoundFunction.applyAsInt(x); - int minYBound = minYBoundFunction.applyAsInt(x); - int y = random.nextInt(maxYBound - minYBound + 1) + minYBound; - List directions = new ArrayList<>(Arrays.asList(0, 1, 2, 3)); - int regressiveDir = random.nextInt(directions.size()); - directions.remove(regressiveDir); - for (int j = 0; j < numSteps; j++) { - if (inBounds(x, y, size)) { - applyAtSymmetryPoints(x, y, SymmetryType.TERRAIN, (sx, sy) -> setPrimitive(sx, sy, true)); - } - switch (directions.get(random.nextInt(directions.size()))) { - case 0 -> x++; - case 1 -> x--; - case 2 -> y++; - case 3 -> y--; + return enqueue(() -> { + + int size = getSize(); + Symmetry symmetry = symmetrySettings.getSymmetry(SymmetryType.TERRAIN); + int minXBound = 0; + int maxXBound = SymmetryUtil.getMaxXBound(symmetry, size); + IntUnaryOperator minYBoundFunction = SymmetryUtil.getMinYBoundFunction(symmetry, size); + IntUnaryOperator maxYBoundFunction = SymmetryUtil.getMaxYBoundFunction(symmetry, size); + for (int i = 0; i < numWalkers; i++) { + int x = random.nextInt(maxXBound - minXBound) + minXBound; + int maxYBound = maxYBoundFunction.applyAsInt(x); + int minYBound = minYBoundFunction.applyAsInt(x); + int y = random.nextInt(maxYBound - minYBound + 1) + minYBound; + List directions = new ArrayList<>(Arrays.asList(0, 1, 2, 3)); + int regressiveDir = random.nextInt(directions.size()); + directions.remove(regressiveDir); + for (int j = 0; j < numSteps; j++) { + if (inBounds(x, y, size)) { + setPrimitive(x, y, true); + } + switch (directions.get(random.nextInt(directions.size()))) { + case 0 -> x++; + case 1 -> x--; + case 2 -> y++; + case 3 -> y--; + } } } - } - return this; + applySymmetry(SymmetryType.TERRAIN); + }); } /** @@ -983,6 +990,15 @@ public boolean isEdge(int x, int y) { x < size - 1 && getPrimitive(x + 1, y) != value) || (y < size - 1 && getPrimitive(x, y + 1) != value)); } + public boolean isEdge(int x, int y, int size, long[] mask) { + boolean value = getBit(x, y, size, mask); + return ((x > 0 && getBit(x - 1, y, size, mask) != value) || (y > 0 && getBit(x, y - 1, size, mask) != value) + || ( + x < size - 1 && getBit(x + 1, y, size, mask) != value) || (y < size - 1 + && getBit(x, y + 1, size, mask) + != value)); + } + /** * Set false pixels with non-like neighbors to true * with a probability of {@code strength} {@code count} times @@ -998,11 +1014,11 @@ public BooleanMask dilute(float strength, int count) { for (int i = 0; i < count; i++) { long[] maskCopy = getMaskCopy(); applyWithSymmetry(symmetryType, (x, y) -> { - if (!getPrimitive(x, y) && random.nextFloat() < strength && isEdge(x, y)) { - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> setBit(sx, sy, true, size, maskCopy)); + if (!getBit(x, y, getSize(), maskCopy) && random.nextFloat() < strength && isEdge(x, y, size, + maskCopy)) { + setBit(x, y, true, size, mask); } }); - mask = maskCopy; } }); } @@ -1020,11 +1036,11 @@ public BooleanMask erode(float strength, int count) { for (int i = 0; i < count; i++) { long[] maskCopy = getMaskCopy(); applyWithSymmetry(symmetryType, (x, y) -> { - if (getPrimitive(x, y) && random.nextFloat() < strength && isEdge(x, y)) { - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> setBit(sx, sy, false, size, maskCopy)); + if (getBit(x, y, getSize(), maskCopy) && random.nextFloat() < strength && isEdge(x, y, size, + maskCopy)) { + setBit(x, y, false, size, mask); } }); - mask = maskCopy; } }); } @@ -1099,10 +1115,12 @@ public BooleanMask limitToSymmetryRegion() { } public BooleanMask limitToSymmetryRegion(SymmetryType symmetryType) { + Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); + int size = getSize(); int minXBound = 0; - int maxXBound = getMaxXBound(symmetryType); - IntUnaryOperator minYBoundFunction = getMinYBoundFunction(symmetryType); - IntUnaryOperator maxYBoundFunction = getMaxYBoundFunction(symmetryType); + int maxXBound = SymmetryUtil.getMaxXBound(symmetry, size); + IntUnaryOperator minYBoundFunction = SymmetryUtil.getMinYBoundFunction(symmetry, size); + IntUnaryOperator maxYBoundFunction = SymmetryUtil.getMaxYBoundFunction(symmetry, size); Map minYBoundMap = IntStream.range(minXBound, maxXBound) .boxed() .collect(Collectors.toMap(Function.identity(), @@ -1172,7 +1190,7 @@ public BooleanMask removeAreasSmallerThan(int maxArea) { Set coordinates = getShapeCoordinates(location, maxArea); seen.addAll(coordinates); if (coordinates.size() < maxArea) { - fillCoordinates(coordinates, !value); + coordinates.forEach(loc -> set((int) loc.getX(), (int) loc.getY(), !value)); } } }); @@ -1345,7 +1363,7 @@ public List getAllCoordinatesEqualTo(boolean value, int spacing) { public List getRandomCoordinates(float minSpacing, float maxSpacing, SymmetryType symmetryType) { List coordinateList; if (symmetryType != null) { - coordinateList = copy().limitToSymmetryRegion().getAllCoordinatesEqualTo(true); + coordinateList = copy().limitToSymmetryRegion(symmetryType).getAllCoordinatesEqualTo(true); } else { coordinateList = getAllCoordinatesEqualTo(true); } @@ -1400,31 +1418,19 @@ public Vector2 getRandomPosition() { } public BooleanMask addPrimitiveWithSymmetry(SymmetryType symmetryType, ToBooleanBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - boolean value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> addPrimitiveAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> addPrimitiveAt(x, y, valueFunction.apply(x, y))); } public BooleanMask subtractPrimitiveWithSymmetry(SymmetryType symmetryType, ToBooleanBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - boolean value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> subtractPrimitiveAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> subtractPrimitiveAt(x, y, valueFunction.apply(x, y))); } public BooleanMask multiplyPrimitiveWithSymmetry(SymmetryType symmetryType, ToBooleanBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - boolean value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> multiplyPrimitiveAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> multiplyPrimitiveAt(x, y, valueFunction.apply(x, y))); } public BooleanMask dividePrimitiveWithSymmetry(SymmetryType symmetryType, ToBooleanBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - boolean value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> dividePrimitiveAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> dividePrimitiveAt(x, y, valueFunction.apply(x, y))); } private BooleanMask applyWithOffset(BooleanMask other, BiIntBooleanConsumer action, int xOffset, int yOffset, @@ -1435,35 +1441,17 @@ private BooleanMask applyWithOffset(BooleanMask other, BiIntBooleanConsumer acti int smallerSize = StrictMath.min(size, otherSize); int biggerSize = StrictMath.max(size, otherSize); if (smallerSize == otherSize) { - if (symmetrySettings.spawnSymmetry().isPerfectSymmetry()) { - Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, - otherSize, size); - Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, - otherSize, size); - other.apply((x, y) -> { - int shiftX = coordinateXMap.get(x); - int shiftY = coordinateYMap.get(y); - if (inBounds(shiftX, shiftY, size)) { - boolean value = other.getPrimitive(x, y); - applyAtSymmetryPoints(shiftX, shiftY, SymmetryType.SPAWN, - (sx, sy) -> action.accept(sx, sy, value)); - } - }); - } else { - applyAtSymmetryPointsWithOutOfBounds(xOffset, yOffset, SymmetryType.SPAWN, (sx, sy) -> { - Map coordinateXMap = getShiftedCoordinateMap(sx, center, wrapEdges, otherSize, - size); - Map coordinateYMap = getShiftedCoordinateMap(sy, center, wrapEdges, otherSize, - size); - other.apply((x, y) -> { - int shiftX = coordinateXMap.get(x); - int shiftY = coordinateYMap.get(y); - if (inBounds(shiftX, shiftY, size)) { - action.accept(shiftX, shiftY, other.getPrimitive(x, y)); - } - }); - }); - } + Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, + otherSize, size); + Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, + otherSize, size); + other.apply((x, y) -> { + int shiftX = coordinateXMap.get(x); + int shiftY = coordinateYMap.get(y); + if (inBounds(shiftX, shiftY, size)) { + action.accept(shiftX, shiftY, other.getPrimitive(x, y)); + } + }); } else { Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, size, otherSize); @@ -1477,6 +1465,7 @@ private BooleanMask applyWithOffset(BooleanMask other, BiIntBooleanConsumer acti } }); } + applySymmetry(SymmetryType.SPAWN); }); } } 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 a0c52ee42..fac80b492 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java @@ -274,7 +274,8 @@ private void waterDrop(int maxIterations, float x, float y, float friction, floa for (int i = 0; i < maxIterations; ++i) { int sampleX = (int) (x + xOffset); int sampleY = (int) (y + yOffset); - if (!inBounds(sampleX, sampleY) || !inBounds((int) xPrev, (int) yPrev)) { + if (!inBounds(sampleX, sampleY, getSize()) || !inBounds((int) xPrev, (int) yPrev, + getSize())) { return; } Vector3 surfaceNormal = calculateNormalAt(sampleX, sampleY, 1f); @@ -668,7 +669,7 @@ protected FloatMask setSizeInternal(int 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)); + setPrimitive(x, y, value); }); } }); @@ -951,38 +952,23 @@ public FloatMask divideWithOffset(FloatMask other, int xOffset, int yOffset, boo } public FloatMask setPrimitiveWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - float value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> setPrimitive(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> setPrimitive(x, y, valueFunction.apply(x, y))); } 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)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> addPrimitiveAt(x, y, valueFunction.apply(x, y))); } 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)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> subtractPrimitiveAt(x, y, valueFunction.apply(x, y))); } public FloatMask multiplyPrimitiveWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - float value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> multiplyPrimitiveAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> multiplyPrimitiveAt(x, y, valueFunction.apply(x, y))); } 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)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> dividePrimitiveAt(x, y, valueFunction.apply(x, y))); } private FloatMask applyWithOffset(FloatMask other, BiIntFloatConsumer action, int xOffset, int yOffset, @@ -993,7 +979,6 @@ private FloatMask applyWithOffset(FloatMask other, BiIntFloatConsumer action, in int smallerSize = StrictMath.min(size, otherSize); int biggerSize = StrictMath.max(size, otherSize); if (smallerSize == otherSize) { - if (symmetrySettings.spawnSymmetry().isPerfectSymmetry()) { Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, otherSize, size); Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, @@ -1002,26 +987,9 @@ private FloatMask applyWithOffset(FloatMask other, BiIntFloatConsumer action, in int shiftX = coordinateXMap.get(x); int shiftY = coordinateYMap.get(y); if (inBounds(shiftX, shiftY, size)) { - float value = other.getPrimitive(x, y); - applyAtSymmetryPoints(shiftX, shiftY, SymmetryType.SPAWN, - (sx, sy) -> action.accept(sx, sy, value)); + action.accept(shiftX, shiftY, other.getPrimitive(x, y)); } }); - } else { - applyAtSymmetryPointsWithOutOfBounds(xOffset, yOffset, SymmetryType.SPAWN, (sx, sy) -> { - Map coordinateXMap = getShiftedCoordinateMap(sx, center, wrapEdges, otherSize, - size); - Map coordinateYMap = getShiftedCoordinateMap(sy, center, wrapEdges, otherSize, - size); - other.apply((x, y) -> { - int shiftX = coordinateXMap.get(x); - int shiftY = coordinateYMap.get(y); - if (inBounds(shiftX, shiftY, size)) { - action.accept(shiftX, shiftY, other.getPrimitive(x, y)); - } - }); - }); - } } else { Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, size, otherSize); @@ -1035,6 +1003,7 @@ private FloatMask applyWithOffset(FloatMask other, BiIntFloatConsumer action, in } }); } + applySymmetry(SymmetryType.SPAWN); }); } } diff --git a/shared/src/main/java/com/faforever/neroxis/mask/IntegerMask.java b/shared/src/main/java/com/faforever/neroxis/mask/IntegerMask.java index bf960d5ac..95169cbea 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/IntegerMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/IntegerMask.java @@ -54,7 +54,7 @@ public IntegerMask(int size, Long seed, SymmetrySettings symmetrySettings, Strin public IntegerMask(BooleanMask other, int low, int high, String name) { this(other.getSize(), other.getNextSeed(), other.getSymmetrySettings(), name, other.isParallel()); enqueue(dependencies -> { - BooleanMask source = (BooleanMask) dependencies.get(0); + BooleanMask source = (BooleanMask) dependencies.getFirst(); apply((x, y) -> setPrimitive(x, y, source.getPrimitive(x, y) ? high : low)); }, other); } @@ -118,7 +118,7 @@ public IntegerMask blur(int radius) { public IntegerMask blur(int radius, BooleanMask other) { assertCompatibleMask(other); return enqueue(dependencies -> { - BooleanMask limiter = (BooleanMask) dependencies.get(0); + BooleanMask limiter = (BooleanMask) dependencies.getFirst(); int[][] innerCount = getInnerCount(); apply((x, y) -> { if (limiter.get(x, y)) { @@ -130,7 +130,7 @@ public IntegerMask blur(int radius, BooleanMask other) { @Override protected IntegerMask copyFrom(IntegerMask other) { - return enqueue(dependencies -> fill(((IntegerMask) dependencies.get(0)).mask), other); + return enqueue(dependencies -> fill(((IntegerMask) dependencies.getFirst()).mask), other); } @Override @@ -211,7 +211,7 @@ protected IntegerMask setSizeInternal(int newSize) { Map coordinateMap = getSymmetricScalingCoordinateMap(oldSize, newSize); applyWithSymmetry(SymmetryType.SPAWN, (x, y) -> { int value = oldMask[coordinateMap.get(x)][coordinateMap.get(y)]; - applyAtSymmetryPoints(x, y, SymmetryType.SPAWN, (sx, sy) -> setPrimitive(sx, sy, value)); + setPrimitive(x, y, value); }); } }); @@ -247,18 +247,22 @@ private IntegerMask divide(ToIntBiIntFunction valueFunction) { return apply((x, y) -> dividePrimitiveAt(x, y, valueFunction.apply(x, y))); } - private void addPrimitiveAt(int x, int y, float value) { + private void addPrimitiveAt(int x, int y, int value) { mask[x][y] += value; } - private void subtractPrimitiveAt(int x, int y, float value) { + private void subtractPrimitiveAt(int x, int y, int value) { mask[x][y] -= value; } - private void multiplyPrimitiveAt(int x, int y, float value) { + private void multiplyPrimitiveAt(int x, int y, int value) { mask[x][y] *= value; } + private void dividePrimitiveAt(int x, int y, int value) { + mask[x][y] /= value; + } + public BufferedImage writeToImage(BufferedImage image, float scaleFactor) { assertSize(image.getHeight()); int size = getSize(); @@ -267,10 +271,6 @@ public BufferedImage writeToImage(BufferedImage image, float scaleFactor) { return image; } - private void dividePrimitiveAt(int x, int y, float value) { - mask[x][y] /= value; - } - @Override protected int[][] getInnerCount() { int[][] innerCount = new int[getSize()][getSize()]; @@ -287,7 +287,7 @@ public Integer getSum() { public IntegerMask add(IntegerMask other) { assertCompatibleMask(other); return enqueue(dependencies -> { - IntegerMask source = (IntegerMask) dependencies.get(0); + IntegerMask source = (IntegerMask) dependencies.getFirst(); apply((x, y) -> mask[x][y] += source.mask[x][y]); }, other); } @@ -302,7 +302,7 @@ public IntegerMask add(BooleanMask other, Integer value) { assertCompatibleMask(other); int val = value; return enqueue(dependencies -> { - BooleanMask source = (BooleanMask) dependencies.get(0); + BooleanMask source = (BooleanMask) dependencies.getFirst(); apply((x, y) -> { if (source.getPrimitive(x, y)) { addPrimitiveAt(x, y, val); @@ -328,7 +328,7 @@ public IntegerMask add(BooleanMask other, IntegerMask values) { @Override public IntegerMask addWithOffset(IntegerMask other, int xOffset, int yOffset, boolean center, boolean wrapEdges) { return enqueue(dependencies -> { - IntegerMask source = (IntegerMask) dependencies.get(0); + IntegerMask source = (IntegerMask) dependencies.getFirst(); applyWithOffset(source, (TriIntConsumer) this::addPrimitiveAt, xOffset, yOffset, center, wrapEdges); }, other); } @@ -349,7 +349,7 @@ public Integer getAvg() { public IntegerMask subtract(IntegerMask other) { assertCompatibleMask(other); return enqueue(dependencies -> { - IntegerMask source = (IntegerMask) dependencies.get(0); + IntegerMask source = (IntegerMask) dependencies.getFirst(); apply((x, y) -> mask[x][y] -= source.mask[x][y]); }, other); } @@ -359,7 +359,7 @@ public IntegerMask subtract(BooleanMask other, Integer values) { assertCompatibleMask(other); int val = values; return enqueue(dependencies -> { - BooleanMask source = (BooleanMask) dependencies.get(0); + BooleanMask source = (BooleanMask) dependencies.getFirst(); apply((x, y) -> { if (source.getPrimitive(x, y)) { subtractPrimitiveAt(x, y, val); @@ -386,7 +386,7 @@ public IntegerMask subtract(BooleanMask other, IntegerMask values) { public IntegerMask subtractWithOffset(IntegerMask other, int xOffset, int yOffset, boolean center, boolean wrapEdges) { return enqueue(dependencies -> { - IntegerMask source = (IntegerMask) dependencies.get(0); + IntegerMask source = (IntegerMask) dependencies.getFirst(); applyWithOffset(source, (TriIntConsumer) this::subtractPrimitiveAt, xOffset, yOffset, center, wrapEdges); }, other); } @@ -395,7 +395,7 @@ public IntegerMask subtractWithOffset(IntegerMask other, int xOffset, int yOffse public IntegerMask multiply(IntegerMask other) { assertCompatibleMask(other); return enqueue(dependencies -> { - IntegerMask source = (IntegerMask) dependencies.get(0); + IntegerMask source = (IntegerMask) dependencies.getFirst(); apply((x, y) -> mask[x][y] *= source.mask[x][y]); }, other); } @@ -410,7 +410,7 @@ public IntegerMask multiply(BooleanMask other, Integer value) { assertCompatibleMask(other); int val = value; return enqueue(dependencies -> { - BooleanMask source = (BooleanMask) dependencies.get(0); + BooleanMask source = (BooleanMask) dependencies.getFirst(); apply((x, y) -> { if (source.getPrimitive(x, y)) { multiplyPrimitiveAt(x, y, val); @@ -437,7 +437,7 @@ public IntegerMask multiply(BooleanMask other, IntegerMask value) { public IntegerMask multiplyWithOffset(IntegerMask other, int xOffset, int yOffset, boolean center, boolean wrapEdges) { return enqueue(dependencies -> { - IntegerMask source = (IntegerMask) dependencies.get(0); + IntegerMask source = (IntegerMask) dependencies.getFirst(); applyWithOffset(source, (TriIntConsumer) this::multiplyPrimitiveAt, xOffset, yOffset, center, wrapEdges); }, other); } @@ -446,7 +446,7 @@ public IntegerMask multiplyWithOffset(IntegerMask other, int xOffset, int yOffse public IntegerMask divide(IntegerMask other) { assertCompatibleMask(other); return enqueue(dependencies -> { - IntegerMask source = (IntegerMask) dependencies.get(0); + IntegerMask source = (IntegerMask) dependencies.getFirst(); apply((x, y) -> mask[x][y] /= source.mask[x][y]); }, other); } @@ -461,7 +461,7 @@ public IntegerMask divide(BooleanMask other, Integer value) { assertCompatibleMask(other); int val = value; return enqueue(dependencies -> { - BooleanMask source = (BooleanMask) dependencies.get(0); + BooleanMask source = (BooleanMask) dependencies.getFirst(); apply((x, y) -> { if (source.getPrimitive(x, y)) { dividePrimitiveAt(x, y, val); @@ -488,37 +488,25 @@ public IntegerMask divide(BooleanMask other, IntegerMask value) { public IntegerMask divideWithOffset(IntegerMask other, int xOffset, int yOffset, boolean center, boolean wrapEdges) { return enqueue(dependencies -> { - IntegerMask source = (IntegerMask) dependencies.get(0); + IntegerMask source = (IntegerMask) dependencies.getFirst(); applyWithOffset(source, (TriIntConsumer) this::dividePrimitiveAt, xOffset, yOffset, center, wrapEdges); }, other); } public IntegerMask addPrimitiveWithSymmetry(SymmetryType symmetryType, ToIntBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - int value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> addPrimitiveAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> addPrimitiveAt(x, y, valueFunction.apply(x, y))); } public IntegerMask subtractPrimitiveWithSymmetry(SymmetryType symmetryType, ToIntBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - int value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> subtractPrimitiveAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> subtractPrimitiveAt(x, y, valueFunction.apply(x, y))); } public IntegerMask multiplyPrimitiveWithSymmetry(SymmetryType symmetryType, ToIntBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - int value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> multiplyPrimitiveAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> multiplyPrimitiveAt(x, y, valueFunction.apply(x, y))); } public IntegerMask dividePrimitiveWithSymmetry(SymmetryType symmetryType, ToIntBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - int value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> dividePrimitiveAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> dividePrimitiveAt(x, y, valueFunction.apply(x, y))); } public IntegerMask applyWithOffset(IntegerMask other, TriIntConsumer action, int xOffset, int yOffset, @@ -529,35 +517,17 @@ public IntegerMask applyWithOffset(IntegerMask other, TriIntConsumer action, int int smallerSize = StrictMath.min(size, otherSize); int biggerSize = StrictMath.max(size, otherSize); if (smallerSize == otherSize) { - if (symmetrySettings.spawnSymmetry().isPerfectSymmetry()) { - Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, - otherSize, size); - Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, - otherSize, size); - other.apply((x, y) -> { - int shiftX = coordinateXMap.get(x); - int shiftY = coordinateYMap.get(y); - if (inBounds(shiftX, shiftY, size)) { - int value = other.getPrimitive(x, y); - applyAtSymmetryPoints(shiftX, shiftY, SymmetryType.SPAWN, - (sx, sy) -> action.accept(x, y, value)); - } - }); - } else { - applyAtSymmetryPointsWithOutOfBounds(xOffset, yOffset, SymmetryType.SPAWN, (sx, sy) -> { - Map coordinateXMap = getShiftedCoordinateMap(sx, center, wrapEdges, otherSize, - size); - Map coordinateYMap = getShiftedCoordinateMap(sy, center, wrapEdges, otherSize, - size); - other.apply((x, y) -> { - int shiftX = coordinateXMap.get(x); - int shiftY = coordinateYMap.get(y); - if (inBounds(shiftX, shiftY, size)) { - action.accept(shiftX, shiftY, other.getPrimitive(x, y)); - } - }); - }); - } + Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, + otherSize, size); + Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, + otherSize, size); + other.apply((x, y) -> { + int shiftX = coordinateXMap.get(x); + int shiftY = coordinateYMap.get(y); + if (inBounds(shiftX, shiftY, size)) { + action.accept(shiftX, shiftY, other.getPrimitive(x, y)); + } + }); } else { Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, size, otherSize); 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 1dad34d17..67e055812 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/Mask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/Mask.java @@ -182,6 +182,12 @@ protected void set(Vector2 location, T value) { set(StrictMath.round(location.getX()), StrictMath.round(location.getY()), value); } + public static boolean inBounds(Vector2 location, int size) { + int x = StrictMath.round(location.getX()); + int y = StrictMath.round(location.getY()); + return inBounds(x, y, size); + } + @SneakyThrows public U immutableCopy() { Mask copy = copy(getName() + MOCK_NAME); @@ -241,6 +247,10 @@ protected U enqueue(Consumer>> function, Mask... usedMasks return (U) this; } + protected void copyValue(int sourceX, int sourceY, int destX, int destY) { + set(destX, destY, get(sourceX, sourceY)); + } + protected void assertMutable() { if (immutable) { throw new IllegalStateException("Mask is a mock and cannot be modified"); @@ -258,16 +268,6 @@ public U init(BooleanMask other, T falseValue, T trueValue) { }, other); } - protected void loop(BiIntConsumer maskAction) { - assertNotPipelined(); - int size = getSize(); - for (int x = 0; x < size; x++) { - for (int y = 0; y < size; y++) { - maskAction.accept(x, y); - } - } - } - protected void assertNotPipelined() { if (parallel && !Pipeline.isRunning()) { throw new IllegalStateException("Mask is pipelined and cannot return an immediate result"); @@ -289,11 +289,27 @@ protected static boolean inBounds(int x, int y, int size) { return x >= 0 && x < size && y >= 0 && y < size; } - public boolean inTeam(int x, int y, boolean reverse) { - return (x >= 0 && - x < getMaxXBound(SymmetryType.TEAM) && - y >= getMinYBoundFunction(SymmetryType.TEAM).applyAsInt(x) && - y < getMaxYBoundFunction(SymmetryType.TEAM).applyAsInt(x)) ^ reverse && inBounds(x, y); + protected U enqueue(Consumer>> function, Mask... usedMasks) { + assertMutable(); + List> dependencies = Arrays.asList(usedMasks); + if (parallel && !Pipeline.isRunning()) { + if (dependencies.stream().anyMatch(dep -> !dep.parallel)) { + throw new IllegalArgumentException("Non parallel masks used as dependents"); + } + Pipeline.add(this, dependencies, function); + } else { + boolean visibleState = visible; + visible = false; + function.accept(dependencies); + visible = visibleState; + if (((DebugUtil.DEBUG && isVisualDebug()) || (DebugUtil.VISUALIZE && !isMock() && !isParallel())) + && visible) { + String callingMethod = DebugUtil.getLastStackTraceMethodInPackage("com.faforever.neroxis.mask"); + String callingLine = DebugUtil.getLastStackTraceLineAfterPackage("com.faforever.neroxis.mask"); + VisualDebugger.visualizeMask(this, callingMethod, callingLine); + } + } + return (U) this; } protected int getMaxXBound(SymmetryType symmetryType) { @@ -351,11 +367,6 @@ public U setToValue(BooleanMask area, U value) { }, area, value); } - public boolean inBounds(int x, int y) { - int size = getSize(); - return inBounds(x, y, getSize()); - } - public boolean onBoundary(Vector2 location) { return onBoundary((int) location.getX(), (int) location.getY()); } @@ -373,10 +384,8 @@ public List getSymmetryPoints(Vector2 point, SymmetryType symmetryType) return getSymmetryPoints(point.getX(), point.getY(), symmetryType); } - public List getSymmetryPoints(float x, float y, SymmetryType symmetryType) { - List symmetryPoints = getSymmetryPointsWithOutOfBounds(x, y, symmetryType); - symmetryPoints.removeIf(point -> !inBounds(point)); - return symmetryPoints; + public boolean inTeam(int x, int y, boolean reverse) { + return inTeamNoBounds(x, y, reverse) && inBounds(x, y, getSize()); } public List getSymmetryPointsWithOutOfBounds(Vector3 point, SymmetryType symmetryType) { @@ -387,62 +396,9 @@ public List getSymmetryPointsWithOutOfBounds(Vector2 point, SymmetryTyp return getSymmetryPointsWithOutOfBounds(point.getX(), point.getY(), symmetryType); } - public List getSymmetryPointsWithOutOfBounds(float x, float y, SymmetryType symmetryType) { - Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); - int numSymPoints = symmetry.getNumSymPoints(); - List symmetryPoints = new ArrayList<>(numSymPoints - 1); - int size = getSize(); - switch (symmetry) { - case POINT2 -> symmetryPoints.add(new Vector2(size - x - 1, size - y - 1)); - case POINT4 -> { - symmetryPoints.add(new Vector2(size - x - 1, size - y - 1)); - symmetryPoints.add(new Vector2(y, size - x - 1)); - symmetryPoints.add(new Vector2(size - y - 1, x)); - } - case POINT6, POINT8, POINT10, POINT12, POINT14, POINT16 -> { - symmetryPoints.add(new Vector2(size - x - 1, size - y - 1)); - for (int i = 1; i < numSymPoints / 2; i++) { - float angle = (float) (2 * StrictMath.PI * i / numSymPoints); - Vector2 rotated = getRotatedPoint(x, y, angle); - symmetryPoints.add(rotated); - Vector2 antiRotated = getRotatedPoint(x, y, (float) (angle + StrictMath.PI)); - symmetryPoints.add(antiRotated); - } - } - 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); - } - } - case X -> symmetryPoints.add(new Vector2(size - x - 1, y)); - case Z -> symmetryPoints.add(new Vector2(x, size - y - 1)); - case XZ -> symmetryPoints.add(new Vector2(y, x)); - case ZX -> symmetryPoints.add(new Vector2(size - y - 1, size - x - 1)); - case QUAD -> { - if (symmetrySettings.teamSymmetry() == Symmetry.Z) { - symmetryPoints.add(new Vector2(x, size - y - 1)); - symmetryPoints.add(new Vector2(size - x - 1, y)); - symmetryPoints.add(new Vector2(size - x - 1, size - y - 1)); - } else { - symmetryPoints.add(new Vector2(size - x - 1, y)); - symmetryPoints.add(new Vector2(x, size - y - 1)); - symmetryPoints.add(new Vector2(size - x - 1, size - y - 1)); - } - } - case DIAG -> { - if (symmetrySettings.teamSymmetry() == Symmetry.ZX) { - symmetryPoints.add(new Vector2(size - y - 1, size - x - 1)); - symmetryPoints.add(new Vector2(y, x)); - symmetryPoints.add(new Vector2(size - x - 1, size - y - 1)); - } else { - symmetryPoints.add(new Vector2(y, x)); - symmetryPoints.add(new Vector2(size - y - 1, size - x - 1)); - symmetryPoints.add(new Vector2(size - x - 1, size - y - 1)); - } - } - } - return symmetryPoints; + public List getSymmetryPoints(float x, float y, SymmetryType symmetryType) { + List symmetryPoints = getSymmetryPointsWithOutOfBounds(x, y, symmetryType); + return symmetryPoints.stream().filter(this::inBounds).toList(); } public ArrayList getSymmetryRotation(float rot) { @@ -522,11 +478,10 @@ public U resample(int newSize) { } } - public boolean inTeamNoBounds(int x, int y, boolean reverse) { - return (x >= 0 && - x < getMaxXBound(SymmetryType.TEAM) && - y >= getMinYBoundFunction(SymmetryType.TEAM).applyAsInt(x) && - y < getMaxYBoundFunction(SymmetryType.TEAM).applyAsInt(x)) ^ reverse; + public List getSymmetryPointsWithOutOfBounds(float x, float y, SymmetryType symmetryType) { + Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); + Symmetry secondarySymmetry = symmetrySettings.getSymmetry(SymmetryType.TEAM); + return SymmetryUtil.getSymmetryPoints(x, y, getSize(), symmetry, secondarySymmetry); } public boolean inTeam(Vector3 pos, boolean reverse) { @@ -549,11 +504,24 @@ public boolean inTeamNoBounds(Vector2 pos, boolean reverse) { return inTeam((int) pos.getX(), (int) pos.getY(), reverse); } + public boolean inTeamNoBounds(int x, int y, boolean reverse) { + Symmetry symmetry = symmetrySettings.getSymmetry(SymmetryType.TEAM); + int size = getSize(); + int maxX = SymmetryUtil.getMaxXBound(symmetry, size); + IntUnaryOperator minYBoundFunction = SymmetryUtil.getMinYBoundFunction(symmetry, size); + IntUnaryOperator maxYBoundFunction = SymmetryUtil.getMaxYBoundFunction(symmetry, size); + return (x >= 0 && x < maxX && y >= minYBoundFunction.applyAsInt(x) && y < maxYBoundFunction.applyAsInt(x)) + ^ reverse; + } + + public boolean inHalf(Vector3 pos, float angle) { + return inHalf(new Vector2(pos), angle); + } + public boolean inHalfNoBounds(Vector2 pos, float angle) { float halfSize = getSize() / 2f; - float vectorAngle = (float) ((new Vector2(halfSize, halfSize).angleTo(pos) * 180f / StrictMath.PI) + - 90f + - 360f) % 360f; + float vectorAngle = + (float) ((new Vector2(halfSize, halfSize).angleTo(pos) * 180f / StrictMath.PI) + 90f + 360f) % 360f; float adjustedAngle = (angle + 180f) % 360f; if (angle >= 180) { return (vectorAngle >= angle || vectorAngle < adjustedAngle); @@ -562,16 +530,17 @@ public boolean inHalfNoBounds(Vector2 pos, float angle) { } } - public boolean inHalf(Vector3 pos, float angle) { - return inHalf(new Vector2(pos), angle); + public T get(Vector2 location) { + return get(StrictMath.round(location.getX()), StrictMath.round(location.getY())); + } + + public boolean inHalfNoBounds(Vector3 pos, float angle) { + return inHalfNoBounds(new Vector2(pos), 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)); - }); + return applySymmetry(symmetryType); } else { if (symmetrySettings.getSymmetry(symmetryType).getNumSymPoints() != 2) { throw new IllegalArgumentException("Symmetry has more than two symmetry points"); @@ -583,21 +552,12 @@ public U forceSymmetry(SymmetryType symmetryType, boolean reverse) { } } - public T get(Vector2 location) { - return get(StrictMath.round(location.getX()), StrictMath.round(location.getY())); - } - - public boolean inHalfNoBounds(Vector3 pos, float angle) { - return inHalfNoBounds(new Vector2(pos), angle); - } - protected U applyWithSymmetry(SymmetryType symmetryType, BiIntConsumer maskAction) { return enqueue(() -> { + int size = getSize(); + Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); loopInSymmetryRegion(symmetryType, maskAction); - if (!symmetrySettings.getSymmetry(symmetryType).isPerfectSymmetry() && - symmetrySettings.spawnSymmetry().isPerfectSymmetry()) { - forceSymmetry(SymmetryType.SPAWN); - } + applySymmetry(symmetryType); }); } @@ -617,11 +577,23 @@ public boolean inHalf(int x, int y, float angle) { return inHalf(new Vector2(x, y), angle); } + protected U applySymmetry(SymmetryType symmetryType) { + return enqueue(() -> { + int size = getSize(); + Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); + loopOutsideSymmetryRegion(symmetryType, (x, y) -> { + Vector2 sourcePoint = SymmetryUtil.getSourcePoint(x, y, size, symmetry); + if (inBounds(sourcePoint, size)) { + copyValue((int) sourcePoint.getX(), (int) sourcePoint.getY(), x, y); + } + }); + }); + } + public boolean inHalf(Vector2 pos, float angle) { float halfSize = getSize() / 2f; - float vectorAngle = (float) ((new Vector2(halfSize, halfSize).angleTo(pos) * 180f / StrictMath.PI) + - 90f + - 360f) % 360f; + float vectorAngle = + (float) ((new Vector2(halfSize, halfSize).angleTo(pos) * 180f / StrictMath.PI) + 90f + 360f) % 360f; float adjustedAngle = (angle + 180f) % 360f; if (angle >= 180) { return (vectorAngle >= angle || vectorAngle < adjustedAngle) && inBounds(pos); @@ -631,7 +603,9 @@ public boolean inHalf(Vector2 pos, float angle) { } public boolean inBounds(Vector2 location) { - return inBounds(StrictMath.round(location.getX()), StrictMath.round(location.getY())); + int x = StrictMath.round(location.getX()); + int y = StrictMath.round(location.getY()); + return inBounds(x, y, getSize()); } public U forceSymmetry(SymmetryType symmetryType) { @@ -648,13 +622,12 @@ public U forceSymmetry() { } protected U setWithSymmetry(SymmetryType symmetryType, BiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - T value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> set(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> set(x, y, valueFunction.apply(x, y))); + } - protected U applyAtSymmetryPointsWithOutOfBounds(Vector2 location, SymmetryType symmetryType, BiIntConsumer action) { + protected U applyAtSymmetryPointsWithOutOfBounds(Vector2 location, SymmetryType symmetryType, + BiIntConsumer action) { return applyAtSymmetryPointsWithOutOfBounds((int) location.getX(), (int) location.getY(), symmetryType, action); } @@ -670,42 +643,25 @@ protected U applyAtSymmetryPoints(int x, int y, SymmetryType symmetryType, BiInt }); } - protected U applyWithOffset(U other, BiIntObjConsumer action, int xOffset, int yOffset, boolean center, boolean wrapEdges) { + protected U applyWithOffset(U other, BiIntObjConsumer action, int xOffset, int yOffset, boolean center, + boolean wrapEdges) { return enqueue(() -> { int size = getSize(); int otherSize = other.getSize(); int smallerSize = StrictMath.min(size, otherSize); int biggerSize = StrictMath.max(size, otherSize); if (smallerSize == otherSize) { - if (symmetrySettings.spawnSymmetry().isPerfectSymmetry()) { - Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, - otherSize, size); - Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, - otherSize, size); - other.apply((x, y) -> { - int shiftX = coordinateXMap.get(x); - int shiftY = coordinateYMap.get(y); - if (inBounds(shiftX, shiftY, size)) { - T value = other.get(x, y); - applyAtSymmetryPoints(shiftX, shiftY, SymmetryType.SPAWN, - (sx, sy) -> action.accept(sx, sy, value)); - } - }); - } else { - applyAtSymmetryPointsWithOutOfBounds(xOffset, yOffset, SymmetryType.SPAWN, (sx, sy) -> { - Map coordinateXMap = getShiftedCoordinateMap(sx, center, wrapEdges, otherSize, - size); - Map coordinateYMap = getShiftedCoordinateMap(sy, center, wrapEdges, otherSize, - size); - other.apply((x, y) -> { - int shiftX = coordinateXMap.get(x); - int shiftY = coordinateYMap.get(y); - if (inBounds(shiftX, shiftY, size)) { - action.accept(shiftX, shiftY, other.get(x, y)); - } - }); - }); - } + Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, + otherSize, size); + Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, + otherSize, size); + other.apply((x, y) -> { + int shiftX = coordinateXMap.get(x); + int shiftY = coordinateYMap.get(y); + if (inBounds(shiftX, shiftY, size)) { + action.accept(shiftX, shiftY, other.get(x, y)); + } + }); } else { Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, size, otherSize); @@ -715,11 +671,11 @@ protected U applyWithOffset(U other, BiIntObjConsumer action, int xOffset, in int shiftX = coordinateXMap.get(x); int shiftY = coordinateYMap.get(y); if (inBounds(shiftX, shiftY, otherSize)) { - T value = other.get(shiftX, shiftY); - action.accept(x, y, value); + action.accept(x, y, other.get(shiftX, shiftY)); } }); } + applySymmetry(SymmetryType.SPAWN); }); } @@ -731,7 +687,9 @@ protected U applyAtSymmetryPointsWithOutOfBounds(int x, int y, SymmetryType symm }); } - protected void populateCoordinateMaps(int xCoordinate, int yCoordinate, boolean center, boolean wrapEdges, int fromSize, int toSize, Map coordinateXMap, Map coordinateYMap) { + protected void populateCoordinateMaps(int xCoordinate, int yCoordinate, boolean center, boolean wrapEdges, + int fromSize, int toSize, Map coordinateXMap, + Map coordinateYMap) { int offsetX; int offsetY; if (center) { @@ -747,7 +705,8 @@ protected void populateCoordinateMaps(int xCoordinate, int yCoordinate, boolean } } - protected Map getShiftedCoordinateMap(int offset, boolean center, boolean wrapEdges, int fromSize, int toSize) { + protected Map getShiftedCoordinateMap(int offset, boolean center, boolean wrapEdges, int fromSize, + int toSize) { int trueOffset; if (center) { trueOffset = offset - fromSize / 2; @@ -760,11 +719,23 @@ protected Map getShiftedCoordinateMap(int offset, boolean cent .collect(Collectors.toMap(i -> i, i -> getShiftedValue(i, trueOffset, toSize, wrapEdges))); } + protected void loop(BiIntConsumer maskAction) { + assertNotPipelined(); + int size = getSize(); + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + maskAction.accept(x, y); + } + } + } + protected void loopInSymmetryRegion(SymmetryType symmetryType, BiIntConsumer maskAction) { assertNotPipelined(); - int maxX = getMaxXBound(symmetryType); - IntUnaryOperator minYBoundFunction = getMinYBoundFunction(symmetryType); - IntUnaryOperator maxYBoundFunction = getMaxYBoundFunction(symmetryType); + Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); + int size = getSize(); + int maxX = SymmetryUtil.getMaxXBound(symmetry, size); + IntUnaryOperator minYBoundFunction = SymmetryUtil.getMinYBoundFunction(symmetry, size); + IntUnaryOperator maxYBoundFunction = SymmetryUtil.getMaxYBoundFunction(symmetry, size); for (int x = 0; x < maxX; x++) { int minY = minYBoundFunction.applyAsInt(x); int maxY = maxYBoundFunction.applyAsInt(x); @@ -774,6 +745,24 @@ protected void loopInSymmetryRegion(SymmetryType symmetryType, BiIntConsumer mas } } + private void loopOutsideSymmetryRegion(SymmetryType symmetryType, BiIntConsumer maskAction) { + assertNotPipelined(); + Symmetry symmetry = symmetrySettings.getSymmetry(SymmetryType.TEAM); + int size = getSize(); + IntUnaryOperator minYBoundFunction = SymmetryUtil.getMinYBoundFunction(symmetry, size); + IntUnaryOperator maxYBoundFunction = SymmetryUtil.getMaxYBoundFunction(symmetry, size); + for (int x = 0; x < size; x++) { + int minY = minYBoundFunction.applyAsInt(x); + int maxY = maxYBoundFunction.applyAsInt(x); + for (int y = 0; y < minY; y++) { + maskAction.accept(x, y); + } + for (int y = maxY; y < size; y++) { + maskAction.accept(x, y); + } + } + } + protected void assertCompatibleMask(Mask other) { int otherSize = other.getSize(); int size = getSize(); @@ -955,10 +944,8 @@ public U fillArc(float x, float y, float startAngle, float endAngle, float radiu dx = x - cx; dy = y - cy; float angle = (float) (StrictMath.atan2(dy, dx) / radiansToDegreeFactor + 360) % 360; - if (inBounds(cx, cy, size) && - dx * dx + dy * dy <= radius2 && - angle >= startAngle && - angle <= endAngle) { + if (inBounds(cx, cy, size) && dx * dx + dy * dy <= radius2 && angle >= startAngle + && angle <= endAngle) { set(cx, cy, value); } } @@ -1043,9 +1030,9 @@ public U fillEdge(int rimWidth, T value) { } protected U fillCoordinates(Collection coordinates, T value) { - coordinates.forEach( - location -> applyAtSymmetryPoints((int) location.getX(), (int) location.getY(), SymmetryType.SPAWN, - (x, y) -> set(x, y, value))); - return (U) this; + return enqueue(() -> { + coordinates.forEach(location -> set((int) location.getX(), (int) location.getY(), value)); + applySymmetry(SymmetryType.SPAWN); + }); } } diff --git a/shared/src/main/java/com/faforever/neroxis/mask/OperationsMask.java b/shared/src/main/java/com/faforever/neroxis/mask/OperationsMask.java index 1f5d860bc..27f212fb1 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/OperationsMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/OperationsMask.java @@ -365,31 +365,19 @@ public U divideWithOffset(U other, int xOffset, int yOffset, boolean center, boo } public U addWithSymmetry(SymmetryType symmetryType, BiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - T value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> addValueAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> addValueAt(x, y, valueFunction.apply(x, y))); } public U subtractWithSymmetry(SymmetryType symmetryType, BiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - T value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> subtractValueAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> subtractValueAt(x, y, valueFunction.apply(x, y))); } public U multiplyWithSymmetry(SymmetryType symmetryType, BiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - T value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> multiplyValueAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> multiplyValueAt(x, y, valueFunction.apply(x, y))); } public U divideWithSymmetry(SymmetryType symmetryType, BiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - T value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> divideValueAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> divideValueAt(x, y, valueFunction.apply(x, y))); } protected void calculateScalarInnerValue(int[][] innerCount, int x, int y, int val) { diff --git a/shared/src/main/java/com/faforever/neroxis/mask/VectorMask.java b/shared/src/main/java/com/faforever/neroxis/mask/VectorMask.java index b00249dfd..3c249aa95 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/VectorMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/VectorMask.java @@ -19,7 +19,12 @@ import java.util.Map; @SuppressWarnings({"unchecked", "UnusedReturnValue", "unused"}) -public abstract sealed class VectorMask, U extends VectorMask> extends OperationsMask permits NormalMask, Vector2Mask, Vector3Mask, Vector4Mask { +public abstract sealed class VectorMask, U extends VectorMask> extends + OperationsMask permits + NormalMask, + Vector2Mask, + Vector3Mask, + Vector4Mask { protected T[][] mask; public VectorMask(BufferedImage sourceImage, Long seed, SymmetrySettings symmetrySettings, float scaleFactor, @@ -83,7 +88,7 @@ public U blur(int radius) { public U blur(int radius, BooleanMask other) { assertCompatibleMask(other); return enqueue(dependencies -> { - BooleanMask limiter = (BooleanMask) dependencies.get(0); + BooleanMask limiter = (BooleanMask) dependencies.getFirst(); T[][] innerCount = getInnerCount(); set((x, y) -> limiter.get(x, y) ? calculateAreaAverage(radius, x, y, innerCount).round().divide(1000) : get( x, y)); @@ -92,7 +97,7 @@ public U blur(int radius, BooleanMask other) { @Override protected U copyFrom(U other) { - return enqueue(dependencies -> fill(((U) dependencies.get(0)).mask), other); + return enqueue(dependencies -> fill(((U) dependencies.getFirst()).mask), other); } @Override @@ -277,7 +282,7 @@ protected void subtractScalarAt(int x, int y, float value) { public U blurComponent(int radius, int component, BooleanMask other) { assertCompatibleMask(other); return enqueue(dependencies -> { - BooleanMask limiter = (BooleanMask) dependencies.get(0); + BooleanMask limiter = (BooleanMask) dependencies.getFirst(); int[][] innerCount = getComponentInnerCount(component); setComponent( (x, y) -> limiter.get(x, y) ? calculateComponentAreaAverage(radius, x, y, innerCount) / 1000f : get( @@ -478,31 +483,19 @@ public U divideComponent(ToFloatBiIntFunction valueFunction, int component) { } public U addScalarWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - float value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> addScalarAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> addScalarAt(x, y, valueFunction.apply(x, y))); } public U subtractScalarWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - float value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> subtractScalarAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> subtractScalarAt(x, y, valueFunction.apply(x, y))); } public U multiplyScalarWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - float value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> multiplyScalarAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> multiplyScalarAt(x, y, valueFunction.apply(x, y))); } public U divideScalarWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction) { - return applyWithSymmetry(symmetryType, (x, y) -> { - float value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> divideScalarAt(sx, sy, value)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> divideScalarAt(x, y, valueFunction.apply(x, y))); } public U addComponent(float value, int component) { @@ -511,35 +504,29 @@ public U addComponent(float value, int component) { public U setComponent(FloatMask other, int component) { return enqueue(dependencies -> { - FloatMask source = (FloatMask) dependencies.get(0); + FloatMask source = (FloatMask) dependencies.getFirst(); setComponent(source::getPrimitive, component); }, other); } public U setComponentWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction, int component) { - return applyWithSymmetry(symmetryType, (x, y) -> { - float value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> setComponentAt(sx, sy, value, component)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> setComponentAt(x, y, valueFunction.apply(x, y), component)); } public U addComponentWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction, int component) { - return applyWithSymmetry(symmetryType, (x, y) -> { - float value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> addComponentAt(sx, sy, value, component)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> addComponentAt(x, y, valueFunction.apply(x, y), component)); } public U addComponent(BooleanMask other, float value, int component) { return enqueue(dependencies -> { - BooleanMask source = (BooleanMask) dependencies.get(0); + BooleanMask source = (BooleanMask) dependencies.getFirst(); addComponent((x, y) -> source.get(x, y) ? value : 0, component); }, other); } public U addComponent(FloatMask other, int component) { return enqueue(dependencies -> { - FloatMask source = (FloatMask) dependencies.get(0); + FloatMask source = (FloatMask) dependencies.getFirst(); addComponent(source::get, component); }, other); } @@ -550,30 +537,26 @@ public U subtractComponent(float value, int component) { public U subtractComponentWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction, int component) { - return applyWithSymmetry(symmetryType, (x, y) -> { - float value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> subtractComponentAt(sx, sy, value, component)); - }); + return applyWithSymmetry(symmetryType, + (x, y) -> subtractComponentAt(x, y, valueFunction.apply(x, y), component)); } public U multiplyComponentWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction, int component) { - return applyWithSymmetry(symmetryType, (x, y) -> { - float value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> multiplyComponentAt(sx, sy, value, component)); - }); + return applyWithSymmetry(symmetryType, + (x, y) -> multiplyComponentAt(x, y, valueFunction.apply(x, y), component)); } public U subtractComponent(BooleanMask other, float value, int component) { return enqueue(dependencies -> { - BooleanMask source = (BooleanMask) dependencies.get(0); + BooleanMask source = (BooleanMask) dependencies.getFirst(); subtractComponent((x, y) -> source.get(x, y) ? value : 0, component); }, other); } public U subtractComponent(FloatMask other, int component) { return enqueue(dependencies -> { - FloatMask source = (FloatMask) dependencies.get(0); + FloatMask source = (FloatMask) dependencies.getFirst(); subtractComponent(source::get, component); }, other); } @@ -584,22 +567,19 @@ public U multiplyComponent(float value, int component) { protected U divideComponentWithSymmetry(SymmetryType symmetryType, ToFloatBiIntFunction valueFunction, int component) { - return applyWithSymmetry(symmetryType, (x, y) -> { - float value = valueFunction.apply(x, y); - applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> divideComponentAt(sx, sy, value, component)); - }); + return applyWithSymmetry(symmetryType, (x, y) -> divideComponentAt(x, y, valueFunction.apply(x, y), component)); } public U multiplyComponent(BooleanMask other, float value, int component) { return enqueue(dependencies -> { - BooleanMask source = (BooleanMask) dependencies.get(0); + BooleanMask source = (BooleanMask) dependencies.getFirst(); multiplyComponent((x, y) -> source.get(x, y) ? value : 0, component); }, other); } public U multiplyComponent(FloatMask other, int component) { return enqueue(dependencies -> { - FloatMask source = (FloatMask) dependencies.get(0); + FloatMask source = (FloatMask) dependencies.getFirst(); multiplyComponent(source::get, component); }, other); } @@ -610,14 +590,14 @@ public U divideComponent(float value, int component) { public U divideComponent(BooleanMask other, float value, int component) { return enqueue(dependencies -> { - BooleanMask source = (BooleanMask) dependencies.get(0); + BooleanMask source = (BooleanMask) dependencies.getFirst(); divideComponent((x, y) -> source.get(x, y) ? value : 0, component); }, other); } public U divideComponent(FloatMask other, int component) { return enqueue(dependencies -> { - FloatMask source = (FloatMask) dependencies.get(0); + FloatMask source = (FloatMask) dependencies.getFirst(); divideComponent(source::get, component); }, other); } @@ -651,82 +631,71 @@ public FloatMask[] splitComponentMasks() { return components; } - public U setComponentWithOffset(FloatMask other, int component, int xOffset, int yOffset, boolean center, boolean wrapEdges) { + public U setComponentWithOffset(FloatMask other, int component, int xOffset, int yOffset, boolean center, + boolean wrapEdges) { return enqueue(dependencies -> { - FloatMask source = (FloatMask) dependencies.get(0); + FloatMask source = (FloatMask) dependencies.getFirst(); applyComponentWithOffset(source, this::setComponentAt, component, xOffset, yOffset, center, wrapEdges); }, other); } - public U addComponentWithOffset(FloatMask other, int component, int xOffset, int yOffset, boolean center, boolean wrapEdges) { + public U addComponentWithOffset(FloatMask other, int component, int xOffset, int yOffset, boolean center, + boolean wrapEdges) { return enqueue(dependencies -> { - FloatMask source = (FloatMask) dependencies.get(0); + FloatMask source = (FloatMask) dependencies.getFirst(); applyComponentWithOffset(source, this::addComponentAt, component, xOffset, yOffset, center, wrapEdges); }, other); } - public U subtractComponentWithOffset(FloatMask other, int component, int xOffset, int yOffset, boolean center, boolean wrapEdges) { + public U subtractComponentWithOffset(FloatMask other, int component, int xOffset, int yOffset, boolean center, + boolean wrapEdges) { return enqueue(dependencies -> { - FloatMask source = (FloatMask) dependencies.get(0); + FloatMask source = (FloatMask) dependencies.getFirst(); applyComponentWithOffset(source, this::subtractComponentAt, component, xOffset, yOffset, center, wrapEdges); }, other); } - public U multiplyComponentWithOffset(FloatMask other, int component, int xOffset, int yOffset, boolean center, boolean wrapEdges) { + public U multiplyComponentWithOffset(FloatMask other, int component, int xOffset, int yOffset, boolean center, + boolean wrapEdges) { return enqueue(dependencies -> { - FloatMask source = (FloatMask) dependencies.get(0); + FloatMask source = (FloatMask) dependencies.getFirst(); applyComponentWithOffset(source, this::multiplyComponentAt, component, xOffset, yOffset, center, wrapEdges); }, other); } - public U divideComponentWithOffset(FloatMask other, int component, int xOffset, int yOffset, boolean center, boolean wrapEdges) { + public U divideComponentWithOffset(FloatMask other, int component, int xOffset, int yOffset, boolean center, + boolean wrapEdges) { return enqueue(dependencies -> { - FloatMask source = (FloatMask) dependencies.get(0); + FloatMask source = (FloatMask) dependencies.getFirst(); applyComponentWithOffset(source, this::divideComponentAt, component, xOffset, yOffset, center, wrapEdges); }, other); } - private U applyComponentWithOffset(FloatMask other, BiIntFloatIntConsumer action, int component, int xOffset, int yOffset, boolean center, boolean wrapEdges) { + private U applyComponentWithOffset(FloatMask other, BiIntFloatIntConsumer action, int component, int xOffset, + int yOffset, boolean center, boolean wrapEdges) { return enqueue(() -> { int size = getSize(); int otherSize = other.getSize(); int smallerSize = StrictMath.min(size, otherSize); int biggerSize = StrictMath.max(size, otherSize); if (smallerSize == otherSize) { - if (symmetrySettings.spawnSymmetry().isPerfectSymmetry()) { - Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, - otherSize, size); - Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, - otherSize, size); - other.apply((x, y) -> { - int shiftX = coordinateXMap.get(x); - int shiftY = coordinateYMap.get(y); - if (inBounds(shiftX, shiftY, size)) { - float value = other.getPrimitive(x, y); - applyAtSymmetryPoints(shiftX, shiftY, SymmetryType.SPAWN, - (sx, sy) -> action.accept(sx, sy, value, component)); - } - }); - } else { - applyAtSymmetryPointsWithOutOfBounds(xOffset, yOffset, SymmetryType.SPAWN, (sx, sy) -> { - Map coordinateXMap = getShiftedCoordinateMap(sx, center, wrapEdges, otherSize, - size); - Map coordinateYMap = getShiftedCoordinateMap(sy, center, wrapEdges, otherSize, - size); - other.apply((x, y) -> { - int shiftX = coordinateXMap.get(x); - int shiftY = coordinateYMap.get(y); - if (inBounds(shiftX, shiftY, size)) { - action.accept(shiftX, shiftY, other.getPrimitive(x, y), component); - } - }); - }); - } + Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, + otherSize, size); + Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, + otherSize, size); + other.apply((x, y) -> { + int shiftX = coordinateXMap.get(x); + int shiftY = coordinateYMap.get(y); + if (inBounds(shiftX, shiftY, size)) { + float value = other.getPrimitive(x, y); + action.accept(shiftX, shiftY, value, component); + } + }); } else { Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, size, otherSize); diff --git a/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java b/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java index 5366a1b30..8660fc32d 100644 --- a/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java +++ b/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java @@ -1,7 +1,10 @@ package com.faforever.neroxis.util; import com.faforever.neroxis.map.Symmetry; +import com.faforever.neroxis.util.vector.Vector2; +import java.util.ArrayList; +import java.util.List; import java.util.function.IntUnaryOperator; public class SymmetryUtil { @@ -37,7 +40,6 @@ public static IntUnaryOperator getMinYBoundFunction(Symmetry symmetry, int size) }; } - public static IntUnaryOperator getMaxYBoundFunction(Symmetry symmetry, int size) { return switch (symmetry) { case POINT3 -> { @@ -46,7 +48,7 @@ public static IntUnaryOperator getMaxYBoundFunction(Symmetry symmetry, int size) float halfSize = size / 2f; yield x -> { //The max Y in the first quadrant is always the halfway point - if (x <= halfSizeBound) { + if (x < halfSizeBound) { return halfSizeBound; } @@ -55,17 +57,18 @@ public static IntUnaryOperator getMaxYBoundFunction(Symmetry symmetry, int size) return MathUtil.clamp(y, 0, halfSizeBound); }; } - case POINT5, POINT6, POINT7, POINT8, POINT9, POINT10, POINT11, POINT12, POINT13, POINT14, POINT15, + case X, QUAD, POINT4, POINT5, POINT6, POINT7, POINT8, POINT9, POINT10, POINT11, POINT12, POINT13, POINT14, + POINT15, POINT16 -> { int halfSizeBound = size / 2 + size % 2; - yield x -> x <= halfSizeBound ? halfSizeBound : 0; + yield x -> x < halfSizeBound ? halfSizeBound : 0; } case ZX, DIAG -> x -> size - x; - case Z, POINT2, POINT4, QUAD -> { - int maxY = size / 2 + size % 2; - yield x -> maxY; + case Z, POINT2 -> { + int halfSizeBound = size / 2 + size % 2; + yield x -> halfSizeBound; } - case X, NONE, XZ -> x -> size; + case NONE, XZ -> x -> size; }; } @@ -75,4 +78,163 @@ static double convertToRotatedRadians(float angle) { return (adjustedAngle / 180) * StrictMath.PI; } + public static List getSymmetryPoints(float x, float y, int size, Symmetry symmetry, + Symmetry secondarySymmetry) { + int numSymPoints = symmetry.getNumSymPoints(); + return switch (symmetry) { + case NONE -> List.of(); + case POINT2 -> List.of(new Vector2(size - x - 1, size - y - 1)); + case POINT4 -> List.of(new Vector2(size - x - 1, size - y - 1), new Vector2(y, size - x - 1), + new Vector2(size - y - 1, x)); + case POINT6, POINT8, POINT10, POINT12, POINT14, POINT16 -> { + List symmetryPoints = new ArrayList<>(numSymPoints - 1); + symmetryPoints.add(new Vector2(size - x - 1, size - y - 1)); + for (int i = 1; i < numSymPoints / 2; i++) { + float angle = (float) (2 * StrictMath.PI * i / numSymPoints); + Vector2 rotated = getRotatedPoint(x, y, size, angle); + symmetryPoints.add(rotated); + Vector2 antiRotated = getRotatedPoint(x, y, size, (float) (angle + StrictMath.PI)); + symmetryPoints.add(antiRotated); + } + yield symmetryPoints; + } + case POINT3, POINT5, POINT7, POINT9, POINT11, POINT13, POINT15 -> { + List symmetryPoints = new ArrayList<>(numSymPoints - 1); + for (int i = 1; i < numSymPoints; i++) { + Vector2 rotated = getRotatedPoint(x, y, size, (float) (2 * StrictMath.PI * i / numSymPoints)); + symmetryPoints.add(rotated); + } + yield symmetryPoints; + } + case X -> List.of(new Vector2(size - x - 1, y)); + case Z -> List.of(new Vector2(x, size - y - 1)); + case XZ -> List.of(new Vector2(y, x)); + case ZX -> List.of(new Vector2(size - y - 1, size - x - 1)); + case QUAD -> { + if (secondarySymmetry == Symmetry.Z) { + yield List.of(new Vector2(x, size - y - 1), new Vector2(size - x - 1, y), + new Vector2(size - x - 1, size - y - 1)); + } else { + yield List.of(new Vector2(size - x - 1, y), new Vector2(x, size - y - 1), + new Vector2(size - x - 1, size - y - 1)); + } + } + case DIAG -> { + if (secondarySymmetry == Symmetry.ZX) { + yield List.of(new Vector2(size - y - 1, size - x - 1), new Vector2(y, x), + new Vector2(size - x - 1, size - y - 1)); + } else { + yield List.of(new Vector2(y, x), new Vector2(size - y - 1, size - x - 1), + new Vector2(size - x - 1, size - y - 1)); + } + } + }; + } + + public static Vector2 getSourcePoint(int x, int y, int size, Symmetry symmetry) { + int halfSizeBound = size / 2 + size % 2; + return switch (symmetry) { + case NONE -> new Vector2(x, y); + case POINT2 -> { + if (y >= halfSizeBound) { + yield new Vector2(size - x - 1, size - y - 1); + } else { + yield new Vector2(x, y); + } + } + case POINT4 -> { + if (x >= halfSizeBound && y >= halfSizeBound) { + yield new Vector2(size - x - 1, size - y - 1); + } else if (x <= halfSizeBound && y >= halfSizeBound) { + yield new Vector2(size - y - 1, x); + } else if (x >= halfSizeBound) { + yield new Vector2(y, size - x - 1); + } else { + yield new Vector2(x, y); + } + } + case POINT6, POINT8, POINT10, POINT12, POINT14, POINT16, POINT3, POINT5, POINT7, POINT9, POINT11, POINT13, + POINT15 -> { + float baseRadians = (float) (StrictMath.PI * 2f / symmetry.getNumSymPoints()); + float dx = x - (size / 2f); + float dy = y - (size / 2f); + + float angle = (float) (StrictMath.atan2(dy, dx) + StrictMath.PI); + + float rawSlice = angle / baseRadians; + int slice = (int) rawSlice; + if (rawSlice == slice) { + slice--; + } + if (slice == 0) { + yield new Vector2(x, y); + } else { + float antiRotateAngle = -slice * baseRadians; + yield getRotatedPoint(x, y, size, antiRotateAngle); + } + } + case X -> { + if (x >= halfSizeBound) { + yield new Vector2(size - x - 1, y); + } else { + yield new Vector2(x, y); + } + } + case Z -> { + if (y >= halfSizeBound) { + yield new Vector2(x, size - y - 1); + } else { + yield new Vector2(x, y); + } + } + case XZ -> { + if (x > y) { + yield new Vector2(y, x); + } else { + yield new Vector2(x, y); + } + } + case ZX -> { + if (y > size - x - 1) { + yield new Vector2(size - y - 1, size - x - 1); + } else { + yield new Vector2(x, y); + } + } + case QUAD -> { + if (x >= halfSizeBound && y >= halfSizeBound) { + yield new Vector2(size - x - 1, size - y - 1); + } else if (x <= halfSizeBound && y >= halfSizeBound) { + yield new Vector2(x, size - y - 1); + } else if (x >= halfSizeBound) { + yield new Vector2(size - x - 1, y); + } else { + yield new Vector2(x, y); + } + } + case DIAG -> { + if (x >= halfSizeBound && y < x && y >= size - x - 1) { + yield new Vector2(size - x - 1, y); + } else if (y <= halfSizeBound && x > y && x <= size - y - 1) { + yield new Vector2(y, x); + } else if (y >= halfSizeBound && x <= y && x > size - y - 1) { + yield new Vector2(x, size - y - 1); + } else { + yield new Vector2(x, y); + } + } + }; + } + + private static Vector2 getRotatedPoint(float x, float y, int size, float radians) { + float halfSize = size / 2f; + float xOffset = x - halfSize; + float yOffset = y - halfSize; + double cosAngle = StrictMath.cos(radians); + double sinAngle = StrictMath.sin(radians); + float newX = (float) (xOffset * cosAngle - yOffset * sinAngle + halfSize); + float newY = (float) (xOffset * sinAngle + yOffset * cosAngle + halfSize); + return new Vector2(newX, newY); + } + } From 42be3d141dabd784f4b3aaf8845c4901c12ca5dd Mon Sep 17 00:00:00 2001 From: Sheikah45 Date: Mon, 15 Jul 2024 19:02:26 -0400 Subject: [PATCH 3/8] WIP --- .../com/faforever/neroxis/mask/FloatMask.java | 22 +++++++++---------- .../neroxis/mask/OperationsMask.java | 4 +++- 2 files changed, 14 insertions(+), 12 deletions(-) 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 fac80b492..cca7cab03 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java @@ -979,17 +979,17 @@ private FloatMask applyWithOffset(FloatMask other, BiIntFloatConsumer action, in int smallerSize = StrictMath.min(size, otherSize); int biggerSize = StrictMath.max(size, otherSize); if (smallerSize == otherSize) { - Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, - otherSize, size); - Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, - otherSize, size); - other.apply((x, y) -> { - int shiftX = coordinateXMap.get(x); - int shiftY = coordinateYMap.get(y); - if (inBounds(shiftX, shiftY, size)) { - action.accept(shiftX, shiftY, other.getPrimitive(x, y)); - } - }); + Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, + otherSize, size); + Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, + otherSize, size); + other.apply((x, y) -> { + int shiftX = coordinateXMap.get(x); + int shiftY = coordinateYMap.get(y); + if (inBounds(shiftX, shiftY, size)) { + action.accept(shiftX, shiftY, other.getPrimitive(x, y)); + } + }); } else { Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, size, otherSize); diff --git a/shared/src/main/java/com/faforever/neroxis/mask/OperationsMask.java b/shared/src/main/java/com/faforever/neroxis/mask/OperationsMask.java index 27f212fb1..3c4bd4345 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/OperationsMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/OperationsMask.java @@ -6,7 +6,9 @@ import com.faforever.neroxis.util.vector.Vector2; @SuppressWarnings({"unchecked", "UnusedReturnValue", "unused"}) -public abstract sealed class OperationsMask> extends Mask permits ComparableMask, VectorMask { +public abstract sealed class OperationsMask> extends Mask permits + ComparableMask, + VectorMask { protected OperationsMask(int size, Long seed, SymmetrySettings symmetrySettings, String name, boolean parallel) { super(size, seed, symmetrySettings, name, parallel); } From 66737476cd77113177e28f0a98034ed9b9414e56 Mon Sep 17 00:00:00 2001 From: Sheikah45 Date: Fri, 13 Sep 2024 12:13:53 -1000 Subject: [PATCH 4/8] Fix issue with quad symmetry --- .../java/com/faforever/neroxis/mask/Mask.java | 3 +- .../faforever/neroxis/util/SymmetryUtil.java | 36 +++++---- .../neroxis/visualization/VisualDebugger.java | 78 +++++++++++++------ 3 files changed, 79 insertions(+), 38 deletions(-) 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 67e055812..e05d6bbd0 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/Mask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/Mask.java @@ -52,7 +52,7 @@ public abstract sealed class Mask> permits OperationsMas private String visualName; protected Mask(U other, String name) { - this(other.getSize(), (name != null && name.endsWith(MOCK_NAME)) ? null : other.getNextSeed(), + this(other.getSize(), other.isMock() ? null : other.getNextSeed(), other.getSymmetrySettings(), name, other.isParallel()); init(other); } @@ -191,6 +191,7 @@ public static boolean inBounds(Vector2 location, int size) { @SneakyThrows public U immutableCopy() { Mask copy = copy(getName() + MOCK_NAME); + copy.setVisualName(getName()); return copy.enqueue(copy::makeImmutable); } diff --git a/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java b/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java index 8660fc32d..3034c7a7d 100644 --- a/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java +++ b/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java @@ -202,25 +202,33 @@ public static Vector2 getSourcePoint(int x, int y, int size, Symmetry symmetry) } } case QUAD -> { - if (x >= halfSizeBound && y >= halfSizeBound) { - yield new Vector2(size - x - 1, size - y - 1); - } else if (x <= halfSizeBound && y >= halfSizeBound) { - yield new Vector2(x, size - y - 1); - } else if (x >= halfSizeBound) { - yield new Vector2(size - x - 1, y); + if (x >= halfSizeBound) { + if (y >= halfSizeBound) { + yield new Vector2(size - x - 1, size - y - 1); + } else { + yield new Vector2(size - x - 1, y); + } } else { - yield new Vector2(x, y); + if (y >= halfSizeBound) { + yield new Vector2(x, size - y - 1); + } else { + yield new Vector2(x, y); + } } } case DIAG -> { - if (x >= halfSizeBound && y < x && y >= size - x - 1) { - yield new Vector2(size - x - 1, y); - } else if (y <= halfSizeBound && x > y && x <= size - y - 1) { - yield new Vector2(y, x); - } else if (y >= halfSizeBound && x <= y && x > size - y - 1) { - yield new Vector2(x, size - y - 1); + if (x > y) { + if (y > size - x - 1) { + yield new Vector2(size - x - 1, size - y - 1); + } else { + yield new Vector2(y, x); + } } else { - yield new Vector2(x, y); + if (y > size - x - 1) { + yield new Vector2(size - y - 1, size - x - 1); + } else { + yield new Vector2(x, y); + } } } }; diff --git a/shared/src/main/java/com/faforever/neroxis/visualization/VisualDebugger.java b/shared/src/main/java/com/faforever/neroxis/visualization/VisualDebugger.java index 803de6096..52949420c 100644 --- a/shared/src/main/java/com/faforever/neroxis/visualization/VisualDebugger.java +++ b/shared/src/main/java/com/faforever/neroxis/visualization/VisualDebugger.java @@ -4,12 +4,19 @@ import javax.swing.*; import java.awt.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class VisualDebugger { private static DefaultListModel listModel; private static JFrame frame; private static JList list; private static EntryPanel canvas; + private static final Map> MASK_ITEMS_BY_NAME = new HashMap<>(); + private static JTextField filter; public static void visualizeMask(Mask mask) { visualizeMask(mask, null, null); @@ -24,7 +31,6 @@ public static void visualizeMask(Mask mask, String method, String line) { SwingUtilities.invokeLater(() -> { createGui(); String name = copyOfmask.getVisualName(); - name = name == null ? copyOfmask.getName() : name; updateList(name + " " + method + " " + line, copyOfmask.immutableCopy()); } ); @@ -56,21 +62,38 @@ private static void setupList() { list.addListSelectionListener(event -> { if (!event.getValueIsAdjusting()) { MaskListItem selectedItem = list.getSelectedValue(); + if (selectedItem == null) { + return; + } updateVisibleCanvas(selectedItem); } }); + + filter = new JTextField(); + filter.addActionListener(event -> refreshList()); + filter.setMinimumSize(new Dimension(350, 50)); + + GridBagConstraints filterConstraints = new GridBagConstraints(); + filterConstraints.fill = GridBagConstraints.HORIZONTAL; + filterConstraints.gridx = 0; + filterConstraints.weightx = 0; + filterConstraints.gridy = 0; + filterConstraints.weighty = 0; + + frame.add(filter, filterConstraints); + JScrollPane listScroller = new JScrollPane(list); listScroller.setMinimumSize(new Dimension(350, 0)); listScroller.setPreferredSize(new Dimension(350, 0)); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.fill = GridBagConstraints.BOTH; - constraints.gridx = 0; - constraints.weightx = 0; - constraints.gridy = 0; - constraints.weighty = 1; + GridBagConstraints scrollerConstraints = new GridBagConstraints(); + scrollerConstraints.fill = GridBagConstraints.BOTH; + scrollerConstraints.gridx = 0; + scrollerConstraints.weightx = 0; + scrollerConstraints.gridy = 1; + scrollerConstraints.weighty = 1; - frame.add(listScroller, constraints); + frame.add(listScroller, scrollerConstraints); } private static void updateVisibleCanvas(MaskListItem maskListItem) { @@ -89,28 +112,37 @@ private static void setupCanvas() { constraints.weightx = 1; constraints.gridy = 0; constraints.weighty = 1; + constraints.gridheight = 2; frame.add(canvas, constraints); } - public synchronized static void updateList(String uniqueMaskName, Mask mask) { - if (!uniqueMaskName.isEmpty()) { - int ind = listModel.getSize(); - for (int i = 0; i < listModel.getSize(); i++) { - if (listModel.get(i).maskName.split(" ")[0].equals(uniqueMaskName.split(" ")[0])) { - ind = i + 1; - } - } + private static void updateList(String uniqueMaskName, Mask mask) { + MASK_ITEMS_BY_NAME.computeIfAbsent(uniqueMaskName, ignored -> new ArrayList<>()) + .add(new MaskListItem(uniqueMaskName, mask)); + refreshList(); + } - listModel.insertElementAt(new MaskListItem(uniqueMaskName, mask), ind); - if (list.getSelectedIndex() == -1) { - list.setSelectedIndex(ind); - } - list.revalidate(); - list.repaint(); + private static void refreshList() { + MaskListItem selectedValue = list.getSelectedValue(); + listModel.clear(); + String text = filter.getText(); + listModel.addAll(MASK_ITEMS_BY_NAME.entrySet() + .stream() + .filter(entry -> text.isBlank() || entry.getKey().contains(text)) + .sorted(Map.Entry.comparingByKey()) + .map(Map.Entry::getValue) + .flatMap( + Collection::stream) + .toList()); + list.revalidate(); + list.repaint(); + int selected = listModel.indexOf(selectedValue); + if (selected != -1) { + list.setSelectedIndex(selected); } } - public record MaskListItem(String maskName, Mask mask) { + private record MaskListItem(String maskName, Mask mask) { @Override public String toString() { return maskName; From eb8c5a2bfcbf0f2e5f5ceba7649cd15882ab4f15 Mon Sep 17 00:00:00 2001 From: Sheikah45 Date: Sun, 15 Sep 2024 13:15:33 -1000 Subject: [PATCH 5/8] Fix issues with pathing --- .idea/inspectionProfiles/Project_Default.xml | 24 +++- .../terrain/BasicTerrainGenerator.java | 17 ++- .../terrain/BigIslandsTerrainGenerator.java | 15 +- .../terrain/DropPlateauTerrainGenerator.java | 17 ++- .../terrain/LandBridgeTerrainGenerator.java | 19 ++- .../terrain/OneIslandTerrainGenerator.java | 31 +++- .../terrain/SmallIslandsTerrainGenerator.java | 17 ++- settings.gradle | 2 - .../neroxis/map/SymmetrySettings.java | 15 ++ .../faforever/neroxis/mask/BooleanMask.java | 45 +++--- .../neroxis/mask/ComparableMask.java | 7 +- .../com/faforever/neroxis/mask/FloatMask.java | 25 ++-- .../faforever/neroxis/mask/IntegerMask.java | 22 +-- .../neroxis/mask/MapMaskMethods.java | 82 +++-------- .../java/com/faforever/neroxis/mask/Mask.java | 132 +++++++++++------- .../faforever/neroxis/mask/NormalMask.java | 14 +- .../neroxis/mask/OperationsMask.java | 8 +- .../faforever/neroxis/mask/PrimitiveMask.java | 7 +- .../faforever/neroxis/mask/Vector2Mask.java | 14 +- .../faforever/neroxis/mask/Vector3Mask.java | 14 +- .../faforever/neroxis/mask/Vector4Mask.java | 14 +- .../faforever/neroxis/mask/VectorMask.java | 44 +++--- .../com/faforever/neroxis/util/Pipeline.java | 10 +- .../faforever/neroxis/util/SymmetryUtil.java | 9 ++ .../SymmetryRegionBoundsChecker.java | 13 ++ .../util/functional/ToBooleanFunction.java | 6 - .../faforever/neroxis/util/vector/Vector.java | 20 ++- .../neroxis/visualization/VisualDebugger.java | 15 +- 28 files changed, 397 insertions(+), 261 deletions(-) create mode 100644 shared/src/main/java/com/faforever/neroxis/util/functional/SymmetryRegionBoundsChecker.java delete mode 100644 shared/src/main/java/com/faforever/neroxis/util/functional/ToBooleanFunction.java diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 312685377..d4b86779c 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -536,7 +536,9 @@ - + + @@ -974,14 +976,19 @@ - + + - + + @@ -991,7 +998,11 @@ - + + + + + @@ -2211,7 +2222,10 @@ - + + diff --git a/generator/src/main/java/com/faforever/neroxis/generator/terrain/BasicTerrainGenerator.java b/generator/src/main/java/com/faforever/neroxis/generator/terrain/BasicTerrainGenerator.java index 9c2fc7f02..603b826ea 100644 --- a/generator/src/main/java/com/faforever/neroxis/generator/terrain/BasicTerrainGenerator.java +++ b/generator/src/main/java/com/faforever/neroxis/generator/terrain/BasicTerrainGenerator.java @@ -3,12 +3,16 @@ import com.faforever.neroxis.brushes.Brushes; import com.faforever.neroxis.generator.GeneratorParameters; import com.faforever.neroxis.map.SCMap; +import com.faforever.neroxis.map.Spawn; import com.faforever.neroxis.map.SymmetrySettings; import com.faforever.neroxis.mask.BooleanMask; import com.faforever.neroxis.mask.FloatMask; import com.faforever.neroxis.mask.MapMaskMethods; +import com.faforever.neroxis.util.vector.Vector2; import com.faforever.neroxis.util.vector.Vector3; +import java.util.List; + public class BasicTerrainGenerator extends TerrainGenerator { protected BooleanMask spawnLandMask; protected BooleanMask spawnPlateauMask; @@ -143,9 +147,18 @@ protected void teamConnectionsSetup() { int numTeammateConnections = 1; connections.setSize(map.getSize() + 1); - MapMaskMethods.connectTeamsAroundCenter(map, random.nextLong(), connections, minMiddlePoints, maxMiddlePoints, + List team0SpawnLocations = map.getSpawns() + .stream() + .filter(spawn -> spawn.getTeamID() == 0) + .map(Spawn::getPosition) + .map(Vector2::new) + .toList(); + + MapMaskMethods.connectTeamsAroundCenter(team0SpawnLocations, random.nextLong(), connections, minMiddlePoints, + maxMiddlePoints, numTeamConnections, maxStepSize, 32); - MapMaskMethods.connectTeammates(map, random.nextLong(), connections, maxMiddlePoints, numTeammateConnections, + MapMaskMethods.connectTeammates(team0SpawnLocations, random.nextLong(), connections, maxMiddlePoints, + numTeammateConnections, maxStepSize); } diff --git a/generator/src/main/java/com/faforever/neroxis/generator/terrain/BigIslandsTerrainGenerator.java b/generator/src/main/java/com/faforever/neroxis/generator/terrain/BigIslandsTerrainGenerator.java index e5fedf95d..f2a4fd183 100644 --- a/generator/src/main/java/com/faforever/neroxis/generator/terrain/BigIslandsTerrainGenerator.java +++ b/generator/src/main/java/com/faforever/neroxis/generator/terrain/BigIslandsTerrainGenerator.java @@ -1,8 +1,12 @@ package com.faforever.neroxis.generator.terrain; import com.faforever.neroxis.generator.ParameterConstraints; +import com.faforever.neroxis.map.Spawn; import com.faforever.neroxis.mask.BooleanMask; import com.faforever.neroxis.mask.MapMaskMethods; +import com.faforever.neroxis.util.vector.Vector2; + +import java.util.List; public class BigIslandsTerrainGenerator extends PathedTerrainGenerator { @@ -24,7 +28,16 @@ protected void landSetup() { BooleanMask islands = new BooleanMask(mapSize / 4, random.nextLong(), symmetrySettings, "islands", true); land.setSize(mapSize + 1); - MapMaskMethods.pathAroundSpawns(map, random.nextLong(), land, maxStepSize, numPaths, maxMiddlePoints, bound, + + List team0SpawnLocations = map.getSpawns() + .stream() + .filter(spawn -> spawn.getTeamID() == 0) + .map(Spawn::getPosition) + .map(Vector2::new) + .toList(); + + MapMaskMethods.pathAroundSpawns(team0SpawnLocations, random.nextLong(), land, maxStepSize, numPaths, + maxMiddlePoints, bound, (float) StrictMath.PI / 2); land.inflate(maxStepSize).setSize(mapSize / 4); diff --git a/generator/src/main/java/com/faforever/neroxis/generator/terrain/DropPlateauTerrainGenerator.java b/generator/src/main/java/com/faforever/neroxis/generator/terrain/DropPlateauTerrainGenerator.java index 04d40b99f..cf5d6e6dc 100644 --- a/generator/src/main/java/com/faforever/neroxis/generator/terrain/DropPlateauTerrainGenerator.java +++ b/generator/src/main/java/com/faforever/neroxis/generator/terrain/DropPlateauTerrainGenerator.java @@ -2,8 +2,12 @@ import com.faforever.neroxis.generator.GeneratorParameters; import com.faforever.neroxis.map.SCMap; +import com.faforever.neroxis.map.Spawn; import com.faforever.neroxis.map.SymmetrySettings; import com.faforever.neroxis.mask.MapMaskMethods; +import com.faforever.neroxis.util.vector.Vector2; + +import java.util.List; public class DropPlateauTerrainGenerator extends PathedTerrainGenerator { @@ -31,9 +35,18 @@ protected void teamConnectionsSetup() { connections.setSize(mapSize + 1); - MapMaskMethods.connectTeamsAroundCenter(map, random.nextLong(), connections, minMiddlePoints, maxMiddlePoints, + List team0SpawnLocations = map.getSpawns() + .stream() + .filter(spawn -> spawn.getTeamID() == 0) + .map(Spawn::getPosition) + .map(Vector2::new) + .toList(); + + MapMaskMethods.connectTeamsAroundCenter(team0SpawnLocations, random.nextLong(), connections, minMiddlePoints, + maxMiddlePoints, numTeamConnections, maxStepSize, 32); - MapMaskMethods.connectTeammates(map, random.nextLong(), connections, maxMiddlePoints, numTeammateConnections, + MapMaskMethods.connectTeammates(team0SpawnLocations, random.nextLong(), connections, maxMiddlePoints, + numTeammateConnections, maxStepSize); } diff --git a/generator/src/main/java/com/faforever/neroxis/generator/terrain/LandBridgeTerrainGenerator.java b/generator/src/main/java/com/faforever/neroxis/generator/terrain/LandBridgeTerrainGenerator.java index 272ad01c9..c600e8a0b 100644 --- a/generator/src/main/java/com/faforever/neroxis/generator/terrain/LandBridgeTerrainGenerator.java +++ b/generator/src/main/java/com/faforever/neroxis/generator/terrain/LandBridgeTerrainGenerator.java @@ -1,7 +1,11 @@ package com.faforever.neroxis.generator.terrain; import com.faforever.neroxis.generator.ParameterConstraints; +import com.faforever.neroxis.map.Spawn; import com.faforever.neroxis.mask.MapMaskMethods; +import com.faforever.neroxis.util.vector.Vector2; + +import java.util.List; public class LandBridgeTerrainGenerator extends PathedTerrainGenerator { @@ -20,9 +24,18 @@ protected void landSetup() { int numPaths = 32 / generatorParameters.spawnCount(); land.setSize(mapSize + 1); - MapMaskMethods.connectTeammates(map, random.nextLong(), land, 8, 2, maxStepSize); - MapMaskMethods.connectTeams(map, random.nextLong(), land, 0, 2, 1, maxStepSize); - MapMaskMethods.pathAroundSpawns(map, random.nextLong(), land, maxStepSize, numPaths, 4, mapSize / 6, + + List team0SpawnLocations = map.getSpawns() + .stream() + .filter(spawn -> spawn.getTeamID() == 0) + .map(Spawn::getPosition) + .map(Vector2::new) + .toList(); + + MapMaskMethods.connectTeammates(team0SpawnLocations, random.nextLong(), land, 8, 2, maxStepSize); + MapMaskMethods.connectTeams(team0SpawnLocations, random.nextLong(), land, 0, 2, 1, maxStepSize); + MapMaskMethods.pathAroundSpawns(team0SpawnLocations, random.nextLong(), land, maxStepSize, numPaths, 4, + mapSize / 6, (float) (StrictMath.PI / 2f)); land.inflate(maxStepSize); land.setSize(mapSize / 8); diff --git a/generator/src/main/java/com/faforever/neroxis/generator/terrain/OneIslandTerrainGenerator.java b/generator/src/main/java/com/faforever/neroxis/generator/terrain/OneIslandTerrainGenerator.java index ada029558..ce70fc3e3 100644 --- a/generator/src/main/java/com/faforever/neroxis/generator/terrain/OneIslandTerrainGenerator.java +++ b/generator/src/main/java/com/faforever/neroxis/generator/terrain/OneIslandTerrainGenerator.java @@ -3,8 +3,12 @@ import com.faforever.neroxis.generator.GeneratorParameters; import com.faforever.neroxis.generator.ParameterConstraints; import com.faforever.neroxis.map.SCMap; +import com.faforever.neroxis.map.Spawn; import com.faforever.neroxis.map.SymmetrySettings; import com.faforever.neroxis.mask.MapMaskMethods; +import com.faforever.neroxis.util.vector.Vector2; + +import java.util.List; public class OneIslandTerrainGenerator extends PathedTerrainGenerator { @@ -31,9 +35,18 @@ protected void teamConnectionsSetup() { int numTeammateConnections = 1; connections.setSize(map.getSize() + 1); - MapMaskMethods.connectTeams(map, random.nextLong(), connections, minMiddlePoints, maxMiddlePoints, + List team0SpawnLocations = map.getSpawns() + .stream() + .filter(spawn -> spawn.getTeamID() == 0) + .map(Spawn::getPosition) + .map(Vector2::new) + .toList(); + + MapMaskMethods.connectTeams(team0SpawnLocations, random.nextLong(), connections, minMiddlePoints, + maxMiddlePoints, numTeamConnections, maxStepSize); - MapMaskMethods.connectTeammates(map, random.nextLong(), connections, maxMiddlePoints, numTeammateConnections, + MapMaskMethods.connectTeammates(team0SpawnLocations, random.nextLong(), connections, maxMiddlePoints, + numTeammateConnections, maxStepSize); } @@ -58,9 +71,19 @@ protected void landSetup() { .fillEdge((int) (mapSize / 8 * (1 - landDensity) + mapSize / 8), false) .inflate(mapSize / 64f) .blur(12, .125f)); - MapMaskMethods.connectTeamsAroundCenter(map, random.nextLong(), land, minMiddlePoints, maxMiddlePoints, + + List team0SpawnLocations = map.getSpawns() + .stream() + .filter(spawn -> spawn.getTeamID() == 0) + .map(Spawn::getPosition) + .map(Vector2::new) + .toList(); + + MapMaskMethods.connectTeamsAroundCenter(team0SpawnLocations, random.nextLong(), land, minMiddlePoints, + maxMiddlePoints, numTeamConnections, maxStepSize, 32); - MapMaskMethods.connectTeammates(map, random.nextLong(), land, maxMiddlePoints, numTeammateConnections, + MapMaskMethods.connectTeammates(team0SpawnLocations, random.nextLong(), land, maxMiddlePoints, + numTeammateConnections, maxStepSize); land.inflate(mapSize / 128f).setSize(mapSize / 8); land.dilute(.5f, 8).erode(.5f, 6); diff --git a/generator/src/main/java/com/faforever/neroxis/generator/terrain/SmallIslandsTerrainGenerator.java b/generator/src/main/java/com/faforever/neroxis/generator/terrain/SmallIslandsTerrainGenerator.java index 21f5841da..5697675a4 100644 --- a/generator/src/main/java/com/faforever/neroxis/generator/terrain/SmallIslandsTerrainGenerator.java +++ b/generator/src/main/java/com/faforever/neroxis/generator/terrain/SmallIslandsTerrainGenerator.java @@ -3,9 +3,13 @@ import com.faforever.neroxis.generator.GeneratorParameters; import com.faforever.neroxis.generator.ParameterConstraints; import com.faforever.neroxis.map.SCMap; +import com.faforever.neroxis.map.Spawn; import com.faforever.neroxis.map.SymmetrySettings; import com.faforever.neroxis.mask.BooleanMask; import com.faforever.neroxis.mask.MapMaskMethods; +import com.faforever.neroxis.util.vector.Vector2; + +import java.util.List; public class SmallIslandsTerrainGenerator extends PathedTerrainGenerator { @Override @@ -28,13 +32,22 @@ protected void landSetup() { int maxMiddlePoints = 4; int numPaths = (int) (4 * landDensity + 4) / symmetrySettings.spawnSymmetry().getNumSymPoints(); - int bound = ((int) (mapSize / 16 * (random.nextFloat() * .25f + landDensity * .75f)) + mapSize / 16); + int bound = ((int) (mapSize / 16f * (random.nextFloat() * .25f + landDensity * .75f)) + mapSize / 16); float maxStepSize = mapSize / 128f; BooleanMask islands = new BooleanMask(mapSize / 4, random.nextLong(), symmetrySettings, "islands", true); land.setSize(mapSize + 1); - MapMaskMethods.pathAroundSpawns(map, random.nextLong(), land, maxStepSize, numPaths, maxMiddlePoints, bound, + + List team0SpawnLocations = map.getSpawns() + .stream() + .filter(spawn -> spawn.getTeamID() == 0) + .map(Spawn::getPosition) + .map(Vector2::new) + .toList(); + + MapMaskMethods.pathAroundSpawns(team0SpawnLocations, random.nextLong(), land, maxStepSize, numPaths, + maxMiddlePoints, bound, (float) StrictMath.PI / 2); land.inflate(maxStepSize).setSize(mapSize / 4); diff --git a/settings.gradle b/settings.gradle index 9fea44e3c..096572879 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,7 +2,5 @@ rootProject.name = 'NeroxisGen' include 'shared' include 'generator' include 'toolsuite' -include 'mapgeneditor' include 'utilities' -include 'ngraph' diff --git a/shared/src/main/java/com/faforever/neroxis/map/SymmetrySettings.java b/shared/src/main/java/com/faforever/neroxis/map/SymmetrySettings.java index 6d19b5c41..77a4e3be6 100644 --- a/shared/src/main/java/com/faforever/neroxis/map/SymmetrySettings.java +++ b/shared/src/main/java/com/faforever/neroxis/map/SymmetrySettings.java @@ -5,6 +5,21 @@ public record SymmetrySettings( Symmetry teamSymmetry, Symmetry spawnSymmetry ) { + + public SymmetrySettings { + if (terrainSymmetry.getNumSymPoints() % teamSymmetry.getNumSymPoints() != 0) { + throw new IllegalArgumentException("Team symmetry not a multiple of terrain symmetry"); + } + + if (terrainSymmetry.getNumSymPoints() % spawnSymmetry.getNumSymPoints() != 0) { + throw new IllegalArgumentException("Spawn symmetry not a multiple of terrain symmetry"); + } + + if (spawnSymmetry.getNumSymPoints() % teamSymmetry.getNumSymPoints() != 0) { + throw new IllegalArgumentException("Spawn symmetry not a multiple of team symmetry"); + } + } + public SymmetrySettings(Symmetry symmetry) { this(symmetry, symmetry, symmetry); } 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 a19f62e7f..7120eb522 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/BooleanMask.java @@ -6,6 +6,7 @@ import com.faforever.neroxis.util.BezierCurve; import com.faforever.neroxis.util.SymmetryUtil; import com.faforever.neroxis.util.functional.BiIntBooleanConsumer; +import com.faforever.neroxis.util.functional.SymmetryRegionBoundsChecker; import com.faforever.neroxis.util.functional.ToBooleanBiIntFunction; import com.faforever.neroxis.util.vector.Vector2; @@ -29,7 +30,7 @@ import static com.faforever.neroxis.brushes.Brushes.loadBrush; @SuppressWarnings({"unchecked", "UnusedReturnValue", "unused"}) -public final class BooleanMask extends PrimitiveMask { +public class BooleanMask extends PrimitiveMask { private static final int BOOLEANS_PER_LONG = 64; private static final long SINGLE_BIT_VALUE = 1; private long[] mask; @@ -56,12 +57,8 @@ public BooleanMask(int size, Long seed, SymmetrySettings symmetrySettings, Strin this(size, seed, symmetrySettings, name, false); } - BooleanMask(BooleanMask other) { - this(other, (String) null); - } - - BooleanMask(BooleanMask other, String name) { - super(other, name); + protected BooleanMask(BooleanMask other, String name, boolean immutable) { + super(other, name, immutable); } private , U extends Comparable> BooleanMask(T other, U minValue) { @@ -126,6 +123,11 @@ private void setPrimitive(int x, int y, boolean value) { setBit(x, y, value, getSize(), mask); } + @Override + protected void copyValue(int sourceX, int sourceY, int destX, int destY) { + setPrimitive(destX, destY, getPrimitive(sourceX, sourceY)); + } + @Override public BooleanMask blur(int radius) { return blur(radius, .5f); @@ -234,7 +236,7 @@ protected BooleanMask setSizeInternal(int newSize) { long[] oldMask = mask; initializeMask(newSize); Map coordinateMap = getSymmetricScalingCoordinateMap(oldSize, newSize); - applyWithSymmetry(SymmetryType.SPAWN, (x, y) -> { + apply((x, y) -> { boolean value = getBit(coordinateMap.get(x), coordinateMap.get(y), oldSize, oldMask); setPrimitive(x, y, value); }); @@ -606,13 +608,15 @@ public BooleanMask randomWalk(int numWalkers, int numSteps) { int maxXBound = SymmetryUtil.getMaxXBound(symmetry, size); IntUnaryOperator minYBoundFunction = SymmetryUtil.getMinYBoundFunction(symmetry, size); IntUnaryOperator maxYBoundFunction = SymmetryUtil.getMaxYBoundFunction(symmetry, size); + SymmetryRegionBoundsChecker symmetryRegionBoundsChecker = SymmetryUtil.getSymmetryRegionBoundsChecker( + symmetry, size); for (int i = 0; i < numWalkers; i++) { int x = random.nextInt(maxXBound - minXBound) + minXBound; int maxYBound = maxYBoundFunction.applyAsInt(x); int minYBound = minYBoundFunction.applyAsInt(x); int y = random.nextInt(maxYBound - minYBound + 1) + minYBound; for (int j = 0; j < numSteps; j++) { - if (inBounds(x, y, size)) { + if (inBounds(x, y, size) && symmetryRegionBoundsChecker.inBounds(x, y)) { setPrimitive(x, y, true); } switch (random.nextInt(4)) { @@ -714,6 +718,9 @@ public BooleanMask path(Vector2 start, Vector2 end, float maxStepSize, int numMi SymmetryType symmetryType) { return enqueue(() -> { int size = getSize(); + Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); + SymmetryRegionBoundsChecker symmetryRegionBoundsChecker = SymmetryUtil.getSymmetryRegionBoundsChecker( + symmetry, size); List checkPoints = new ArrayList<>(); checkPoints.add(new Vector2(start)); for (int i = 0; i < numMiddlePoints; i++) { @@ -737,9 +744,11 @@ public BooleanMask path(Vector2 start, Vector2 end, float maxStepSize, int numMi Vector2 nextLoc = checkPoints.get(i + 1); float oldAngle = location.angleTo(nextLoc) + (random.nextFloat() - .5f) * 2f * maxAngleError; while (location.getDistance(nextLoc) > maxStepSize && numSteps < size * size) { - List symmetryPoints = getSymmetryPoints(location, symmetryType); - if (inBounds(location) && symmetryPoints.stream().allMatch(this::inBounds)) { - setPrimitive((int) location.getX(), (int) location.getY(), true); + if (inBounds(location, size) && symmetryRegionBoundsChecker.inBounds(location)) { + List symmetryPoints = getSymmetryPoints(location, symmetryType); + if (symmetryPoints.stream().allMatch(this::inBounds)) { + setPrimitive(location, true); + } } float magnitude = StrictMath.max(1, random.nextFloat() * maxStepSize); float angle = oldAngle * .5f + location.angleTo(nextLoc) * .5f @@ -752,7 +761,7 @@ public BooleanMask path(Vector2 start, Vector2 end, float maxStepSize, int numMi break; } } - applySymmetry(SymmetryType.TERRAIN); + applySymmetry(symmetryType); }); } @@ -834,6 +843,8 @@ public BooleanMask progressiveWalk(int numWalkers, int numSteps) { int maxXBound = SymmetryUtil.getMaxXBound(symmetry, size); IntUnaryOperator minYBoundFunction = SymmetryUtil.getMinYBoundFunction(symmetry, size); IntUnaryOperator maxYBoundFunction = SymmetryUtil.getMaxYBoundFunction(symmetry, size); + SymmetryRegionBoundsChecker symmetryRegionBoundsChecker = SymmetryUtil.getSymmetryRegionBoundsChecker( + symmetry, size); for (int i = 0; i < numWalkers; i++) { int x = random.nextInt(maxXBound - minXBound) + minXBound; int maxYBound = maxYBoundFunction.applyAsInt(x); @@ -843,7 +854,7 @@ public BooleanMask progressiveWalk(int numWalkers, int numSteps) { int regressiveDir = random.nextInt(directions.size()); directions.remove(regressiveDir); for (int j = 0; j < numSteps; j++) { - if (inBounds(x, y, size)) { + if (inBounds(x, y, size) && symmetryRegionBoundsChecker.inBounds(x, y)) { setPrimitive(x, y, true); } switch (directions.get(random.nextInt(directions.size()))) { @@ -935,7 +946,7 @@ public BooleanMask erode(float strength) { * @return the modified mask */ public BooleanMask acid(float strength, float size) { - BooleanMask holes = new BooleanMask(this, getName() + "holes"); + BooleanMask holes = new BooleanMask(this, getName() + "holes", false); holes.randomize(strength, SymmetryType.SPAWN).inflate(size); return enqueue(dependencies -> { BooleanMask source = (BooleanMask) dependencies.getFirst(); @@ -950,7 +961,7 @@ public BooleanMask acid(float strength, float size) { * @return the modified mask */ public BooleanMask splat(float strength, float size) { - BooleanMask holes = new BooleanMask(this, getName() + "splat"); + BooleanMask holes = new BooleanMask(this, getName() + "splat", false); holes.randomize(strength, SymmetryType.SPAWN).inflate(size); return enqueue(dependencies -> { BooleanMask source = (BooleanMask) dependencies.getFirst(); @@ -1141,7 +1152,7 @@ public BooleanMask limitToSymmetryRegion(SymmetryType symmetryType) { */ public BooleanMask limitToCenteredCircle(float circleRadius) { int size = getSize(); - BooleanMask symmetryLimit = new BooleanMask(size, null, symmetrySettings, getName() + "symmetryLimit", + BooleanMask symmetryLimit = new BooleanMask(size, null, symmetrySettings, getName() + "SymmetryLimit", isParallel()); symmetryLimit.fillCircle(size / 2f, size / 2f, circleRadius, true); return multiply(symmetryLimit); diff --git a/shared/src/main/java/com/faforever/neroxis/mask/ComparableMask.java b/shared/src/main/java/com/faforever/neroxis/mask/ComparableMask.java index f5641aa8d..b6a9f2521 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/ComparableMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/ComparableMask.java @@ -3,13 +3,14 @@ import com.faforever.neroxis.map.SymmetrySettings; @SuppressWarnings({"unchecked", "UnusedReturnValue", "unused"}) -public abstract sealed class ComparableMask, U extends ComparableMask> extends OperationsMask permits PrimitiveMask { +public abstract class ComparableMask, U extends ComparableMask> extends + OperationsMask { protected ComparableMask(int size, Long seed, SymmetrySettings symmetrySettings, String name, boolean parallel) { super(size, seed, symmetrySettings, name, parallel); } - protected ComparableMask(U other, String name) { - super(other, name); + protected ComparableMask(U other, String name, boolean immutable) { + super(other, name, immutable); } protected boolean valueAtEqualTo(int x, int y, T 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 cca7cab03..81525b892 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/FloatMask.java @@ -25,7 +25,7 @@ import static com.faforever.neroxis.brushes.Brushes.loadBrush; @SuppressWarnings({"unchecked", "UnusedReturnValue", "unused"}) -public final class FloatMask extends PrimitiveMask { +public class FloatMask extends PrimitiveMask { private float[][] mask; public FloatMask(int size, Long seed, SymmetrySettings symmetrySettings) { @@ -70,19 +70,11 @@ public FloatMask(BufferedImage sourceImage, Long seed, SymmetrySettings symmetry this(sourceImage, seed, symmetrySettings, scaleFactor, name, false); } - FloatMask(FloatMask other) { - this(other, null); - } - - FloatMask(FloatMask other, String name) { - super(other, name); - } - - FloatMask(BooleanMask other, float low, float high) { + protected FloatMask(BooleanMask other, float low, float high) { this(other, low, high, null); } - FloatMask(BooleanMask other, float low, float high, String name) { + protected FloatMask(BooleanMask other, float low, float high, String name) { this(other.getSize(), other.getNextSeed(), other.getSymmetrySettings(), name, other.isParallel()); enqueue(dependencies -> { BooleanMask source = (BooleanMask) dependencies.getFirst(); @@ -90,6 +82,10 @@ public FloatMask(BufferedImage sourceImage, Long seed, SymmetrySettings symmetry }, other); } + protected FloatMask(FloatMask other, String name, boolean immutable) { + super(other, name, immutable); + } + private , U extends VectorMask> FloatMask(VectorMask other1, VectorMask other2) { this(other1, other2, null); @@ -137,6 +133,11 @@ void setPrimitive(int x, int y, float value) { mask[x][y] = value; } + @Override + protected void copyValue(int sourceX, int sourceY, int destX, int destY) { + setPrimitive(destX, destY, getPrimitive(sourceX, sourceY)); + } + /** * Add perlin noise to the mask with the given resolution and noise scale * @@ -667,7 +668,7 @@ protected FloatMask setSizeInternal(int newSize) { float[][] oldMask = mask; initializeMask(newSize); Map coordinateMap = getSymmetricScalingCoordinateMap(oldSize, newSize); - applyWithSymmetry(SymmetryType.SPAWN, (x, y) -> { + apply((x, y) -> { float value = oldMask[coordinateMap.get(x)][coordinateMap.get(y)]; setPrimitive(x, y, value); }); diff --git a/shared/src/main/java/com/faforever/neroxis/mask/IntegerMask.java b/shared/src/main/java/com/faforever/neroxis/mask/IntegerMask.java index 95169cbea..2de35ac92 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/IntegerMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/IntegerMask.java @@ -15,7 +15,7 @@ import java.util.Map; @SuppressWarnings({"UnusedReturnValue", "unused"}) -public final class IntegerMask extends PrimitiveMask { +public class IntegerMask extends PrimitiveMask { private int[][] mask; public IntegerMask(int size, Long seed, SymmetrySettings symmetrySettings) { @@ -39,14 +39,6 @@ public IntegerMask(int size, Long seed, SymmetrySettings symmetrySettings, Strin this(size, seed, symmetrySettings, name, false); } - IntegerMask(IntegerMask other) { - this(other, null); - } - - IntegerMask(IntegerMask other, String name) { - super(other, name); - } - IntegerMask(BooleanMask other, int low, int high) { this(other, low, high, null); } @@ -75,10 +67,19 @@ public IntegerMask(BufferedImage sourceImage, Long seed, SymmetrySettings symmet this(sourceImage, seed, symmetrySettings, null, false); } + protected IntegerMask(IntegerMask other, String name, boolean immutable) { + super(other, name, immutable); + } + private void setPrimitive(int x, int y, int value) { mask[x][y] = value; } + @Override + protected void copyValue(int sourceX, int sourceY, int destX, int destY) { + setPrimitive(destX, destY, getPrimitive(sourceX, sourceY)); + } + public int getPrimitive(Vector2 location) { return getPrimitive(StrictMath.round(location.getX()), StrictMath.round(location.getY())); } @@ -209,7 +210,7 @@ protected IntegerMask setSizeInternal(int newSize) { int[][] oldMask = mask; initializeMask(newSize); Map coordinateMap = getSymmetricScalingCoordinateMap(oldSize, newSize); - applyWithSymmetry(SymmetryType.SPAWN, (x, y) -> { + apply((x, y) -> { int value = oldMask[coordinateMap.get(x)][coordinateMap.get(y)]; setPrimitive(x, y, value); }); @@ -541,6 +542,7 @@ public IntegerMask applyWithOffset(IntegerMask other, TriIntConsumer action, int } }); } + applySymmetry(SymmetryType.SPAWN); }); } } diff --git a/shared/src/main/java/com/faforever/neroxis/mask/MapMaskMethods.java b/shared/src/main/java/com/faforever/neroxis/mask/MapMaskMethods.java index 1772a8af3..8ed13ad5b 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/MapMaskMethods.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/MapMaskMethods.java @@ -1,63 +1,51 @@ package com.faforever.neroxis.mask; -import com.faforever.neroxis.map.SCMap; -import com.faforever.neroxis.map.Spawn; import com.faforever.neroxis.map.SymmetryType; import com.faforever.neroxis.util.vector.Vector2; -import com.faforever.neroxis.util.vector.Vector3; import java.util.ArrayList; import java.util.List; import java.util.Random; -import java.util.stream.Collectors; public class MapMaskMethods { private MapMaskMethods() { } - public static BooleanMask connectTeams(SCMap map, long seed, BooleanMask exec, int minMiddlePoints, + public static BooleanMask connectTeams(List team0SpawnLocations, long seed, BooleanMask exec, + int minMiddlePoints, int maxMiddlePoints, int numConnections, float maxStepSize) { Random random = new Random(seed); - List startTeamSpawns = map.getSpawns() - .stream() - .filter(spawn -> spawn.getTeamID() == 0) - .collect(Collectors.toList()); for (int i = 0; i < numConnections; ++i) { - Spawn startSpawn = startTeamSpawns.get(random.nextInt(startTeamSpawns.size())); int numMiddlePoints; if (maxMiddlePoints > minMiddlePoints) { numMiddlePoints = random.nextInt(maxMiddlePoints - minMiddlePoints) + minMiddlePoints; } else { numMiddlePoints = maxMiddlePoints; } - Vector2 start = new Vector2(startSpawn.getPosition()); - Vector2 end = new Vector2(start); + Vector2 start = team0SpawnLocations.get(random.nextInt(team0SpawnLocations.size())).copy(); + Vector2 end = start.copy(); float maxMiddleDistance = start.getDistance(end); exec.connect(start, end, maxStepSize, numMiddlePoints, maxMiddleDistance, maxMiddleDistance / 2, - (float) (StrictMath.PI / 2), SymmetryType.SPAWN); + (float) (StrictMath.PI / 2), SymmetryType.TERRAIN); } return exec; } - public static BooleanMask connectTeamsAroundCenter(SCMap map, long seed, BooleanMask exec, int minMiddlePoints, + public static BooleanMask connectTeamsAroundCenter(List team0SpawnLocations, long seed, BooleanMask exec, + int minMiddlePoints, int maxMiddlePoints, int numConnections, float maxStepSize, int bound) { - List startTeamSpawns = map.getSpawns() - .stream() - .filter(spawn -> spawn.getTeamID() == 0) - .collect(Collectors.toList()); return exec.enqueue(() -> { Random random = new Random(seed); for (int i = 0; i < numConnections; ++i) { - Spawn startSpawn = startTeamSpawns.get(random.nextInt(startTeamSpawns.size())); int numMiddlePoints; if (maxMiddlePoints > minMiddlePoints) { numMiddlePoints = random.nextInt(maxMiddlePoints - minMiddlePoints) + minMiddlePoints; } else { numMiddlePoints = maxMiddlePoints; } - Vector2 start = new Vector2(startSpawn.getPosition()); - Vector2 end = new Vector2(start); + Vector2 start = team0SpawnLocations.get(random.nextInt(team0SpawnLocations.size())).copy(); + Vector2 end = start.copy(); float offCenterAngle = (float) (StrictMath.PI * (1f / 3f + random.nextFloat() / 3f)); offCenterAngle *= random.nextBoolean() ? 1 : -1; offCenterAngle += start.angleTo(new Vector2(exec.getSize() / 2f, exec.getSize() / 2f)); @@ -65,28 +53,25 @@ public static BooleanMask connectTeamsAroundCenter(SCMap map, long seed, Boolean end.clampMax(exec.getSize() - bound).clampMin(bound); float maxMiddleDistance = start.getDistance(end); exec.connect(start, end, maxStepSize, numMiddlePoints, maxMiddleDistance, maxMiddleDistance / 2, - (float) (StrictMath.PI / 2), SymmetryType.SPAWN); + (float) (StrictMath.PI / 2), SymmetryType.TERRAIN); } }); } - public static BooleanMask connectTeammates(SCMap map, long seed, BooleanMask exec, int maxMiddlePoints, + public static BooleanMask connectTeammates(List team0SpawnLocations, long seed, BooleanMask exec, + int maxMiddlePoints, int numConnections, float maxStepSize) { - List startTeamSpawns = map.getSpawns() - .stream() - .filter(spawn -> spawn.getTeamID() == 0) - .collect(Collectors.toList()); return exec.enqueue(() -> { Random random = new Random(seed); - if (startTeamSpawns.size() > 1) { - startTeamSpawns.forEach(startSpawn -> { + if (team0SpawnLocations.size() > 1) { + team0SpawnLocations.forEach(startSpawn -> { for (int i = 0; i < numConnections; ++i) { - ArrayList otherSpawns = new ArrayList<>(startTeamSpawns); + ArrayList otherSpawns = new ArrayList<>(team0SpawnLocations); otherSpawns.remove(startSpawn); - Spawn endSpawn = otherSpawns.get(random.nextInt(otherSpawns.size())); + Vector2 endSpawn = otherSpawns.get(random.nextInt(otherSpawns.size())); int numMiddlePoints = random.nextInt(maxMiddlePoints); - Vector2 start = new Vector2(startSpawn.getPosition()); - Vector2 end = new Vector2(endSpawn.getPosition()); + Vector2 start = startSpawn.copy(); + Vector2 end = endSpawn.copy(); float maxMiddleDistance = start.getDistance(end) / numMiddlePoints * 2; exec.path(start, end, maxStepSize, numMiddlePoints, maxMiddleDistance, 0, (float) (StrictMath.PI / 2), SymmetryType.TERRAIN); @@ -132,12 +117,13 @@ public static BooleanMask pathInEdgeBounds(long seed, BooleanMask exec, float ma }); } - public static BooleanMask pathAroundSpawns(SCMap map, long seed, BooleanMask exec, float maxStepSize, int numPaths, + public static BooleanMask pathAroundSpawns(List team0SpawnLocations, long seed, BooleanMask exec, + float maxStepSize, int numPaths, int maxMiddlePoints, int bound, float maxAngleError) { return exec.enqueue(() -> { Random random = new Random(seed); - map.getSpawns().forEach(spawn -> { - Vector2 start = new Vector2(spawn.getPosition()); + team0SpawnLocations.forEach(spawn -> { + Vector2 start = spawn.copy(); for (int i = 0; i < numPaths; i++) { int endX = (int) (random.nextFloat() * bound + start.getX()); int endY = (int) (random.nextFloat() * bound + start.getY()); @@ -145,31 +131,9 @@ public static BooleanMask pathAroundSpawns(SCMap map, long seed, BooleanMask exe int numMiddlePoints = random.nextInt(maxMiddlePoints); float maxMiddleDistance = start.getDistance(end) / numMiddlePoints * 2; exec.path(start, end, maxStepSize, numMiddlePoints, maxMiddleDistance, 0, maxAngleError, - SymmetryType.TERRAIN); + SymmetryType.SPAWN); } }); }); } - - public static BooleanMask fillSpawnCircle(SCMap map, BooleanMask exec, float radius) { - return exec.enqueue(() -> { - map.getSpawns().forEach(spawn -> { - Vector3 location = spawn.getPosition(); - exec.fillCircle(location, radius, true); - }); - }); - } - - public static BooleanMask fillSpawnCircleWithProbability(SCMap map, long seed, BooleanMask exec, float spawnSize, - float probability) { - return exec.enqueue(() -> { - Random random = new Random(seed); - if (random.nextFloat() < probability) { - map.getSpawns().forEach(spawn -> { - Vector3 location = spawn.getPosition(); - exec.fillCircle(location, spawnSize, true); - }); - } - }); - } } 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 e05d6bbd0..f2312f326 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/Mask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/Mask.java @@ -20,25 +20,27 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.Set; import java.util.function.Consumer; import java.util.function.IntUnaryOperator; import java.util.stream.Collectors; import java.util.stream.IntStream; @SuppressWarnings({"unchecked", "UnusedReturnValue", "unused"}) -public abstract sealed class Mask> permits OperationsMask { - private static final String MOCK_NAME = "Mock"; +public abstract class Mask> { private static final String COPY_NAME = "Copy"; protected final Random random; @Getter private final String name; @Getter protected final SymmetrySettings symmetrySettings; - private boolean immutable; + @Getter + private final boolean immutable; private int plannedSize; @Getter @Setter @@ -47,14 +49,22 @@ public abstract sealed class Mask> permits OperationsMas @Setter private boolean visualDebug; private boolean visible; - private boolean mock; @Setter private String visualName; + private int copyCount; - protected Mask(U other, String name) { - this(other.getSize(), other.isMock() ? null : other.getNextSeed(), - other.getSymmetrySettings(), name, other.isParallel()); + protected Mask(U other, String name, boolean immutable) { + int size = other.getSize(); + this.symmetrySettings = other.getSymmetrySettings(); + this.name = name == null ? String.valueOf(hashCode()) : name; + this.plannedSize = size; + this.parallel = other.isParallel(); + Long seed = immutable ? null : other.getNextSeed(); + random = seed != null ? new Random(seed) : null; + visible = !immutable; + initializeMask(size); init(other); + this.immutable = immutable; } protected Mask(int size, Long seed, SymmetrySettings symmetrySettings, String name, boolean parallel) { @@ -62,6 +72,7 @@ protected Mask(int size, Long seed, SymmetrySettings symmetrySettings, String na this.name = name == null ? String.valueOf(hashCode()) : name; this.plannedSize = size; this.parallel = parallel; + this.immutable = false; random = seed != null ? new Random(seed) : null; visible = true; initializeMask(size); @@ -110,10 +121,6 @@ public U init(U other) { protected abstract U copyFrom(U other); - public boolean isMock() { - return (name != null && name.endsWith(MOCK_NAME)) || mock; - } - public int getSize() { if (parallel && !Pipeline.isRunning()) { return plannedSize; @@ -188,13 +195,6 @@ public static boolean inBounds(Vector2 location, int size) { return inBounds(x, y, size); } - @SneakyThrows - public U immutableCopy() { - Mask copy = copy(getName() + MOCK_NAME); - copy.setVisualName(getName()); - return copy.enqueue(copy::makeImmutable); - } - protected abstract U fill(T value); protected abstract T getZeroValue(); @@ -211,11 +211,6 @@ protected U enqueue(Runnable function) { return enqueue(ignored -> function.run()); } - private void makeImmutable() { - immutable = true; - mock = true; - } - /** * Set the mask to all zeros * @@ -248,14 +243,20 @@ protected U enqueue(Consumer>> function, Mask... usedMasks return (U) this; } - protected void copyValue(int sourceX, int sourceY, int destX, int destY) { - set(destX, destY, get(sourceX, sourceY)); + private void copyValue(Vector2 source, Vector2 dest) { + copyValue((int) source.getX(), (int) source.getY(), (int) dest.getX(), (int) dest.getY()); } - protected void assertMutable() { - if (immutable) { - throw new IllegalStateException("Mask is a mock and cannot be modified"); - } + private void copyValue(Vector2 source, int destX, int destY) { + copyValue((int) source.getX(), (int) source.getY(), destX, destY); + } + + private void copyValue(int sourceX, int sourceY, Vector2 dest) { + copyValue(sourceX, sourceY, (int) dest.getX(), (int) dest.getY()); + } + + protected void copyValue(int sourceX, int sourceY, int destX, int destY) { + set(destX, destY, get(sourceX, sourceY)); } protected abstract U setSizeInternal(int newSize); @@ -291,7 +292,9 @@ protected static boolean inBounds(int x, int y, int size) { } protected U enqueue(Consumer>> function, Mask... usedMasks) { - assertMutable(); + if (immutable) { + throw new IllegalStateException("Mask is immutable and cannot be modified"); + } List> dependencies = Arrays.asList(usedMasks); if (parallel && !Pipeline.isRunning()) { if (dependencies.stream().anyMatch(dep -> !dep.parallel)) { @@ -303,7 +306,7 @@ protected U enqueue(Consumer>> function, Mask... usedMasks visible = false; function.accept(dependencies); visible = visibleState; - if (((DebugUtil.DEBUG && isVisualDebug()) || (DebugUtil.VISUALIZE && !isMock() && !isParallel())) + if (((DebugUtil.DEBUG && isVisualDebug()) || (DebugUtil.VISUALIZE && !isImmutable() && !isParallel())) && visible) { String callingMethod = DebugUtil.getLastStackTraceMethodInPackage("com.faforever.neroxis.mask"); String callingLine = DebugUtil.getLastStackTraceLineAfterPackage("com.faforever.neroxis.mask"); @@ -579,16 +582,41 @@ public boolean inHalf(int x, int y, float angle) { } protected U applySymmetry(SymmetryType symmetryType) { - return enqueue(() -> { - int size = getSize(); - Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); - loopOutsideSymmetryRegion(symmetryType, (x, y) -> { - Vector2 sourcePoint = SymmetryUtil.getSourcePoint(x, y, size, symmetry); - if (inBounds(sourcePoint, size)) { - copyValue((int) sourcePoint.getX(), (int) sourcePoint.getY(), x, y); - } + Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); + if (symmetry == Symmetry.NONE) { + return (U) this; + } + + if (symmetry.isPerfectSymmetry()) { + return enqueue(() -> { + int size = getSize(); + loopOutsideSymmetryRegion(symmetryType, (x, y) -> { + Vector2 sourcePoint = SymmetryUtil.getSourcePoint(x, y, size, symmetry); + copyValue(sourcePoint, x, y); + }); }); - }); + } else { + return enqueue(() -> { + Set sourcedPoints = new HashSet<>(); + int size = getSize(); + loopOutsideSymmetryRegion(symmetryType, (x, y) -> { + Vector2 sourcePoint = SymmetryUtil.getSourcePoint(x, y, size, symmetry); + sourcedPoints.add(sourcePoint); + if (inBounds(sourcePoint)) { + copyValue(sourcePoint, x, y); + } + }); + loopInSymmetryRegion(symmetryType, (x, y) -> { + if (!sourcedPoints.contains(new Vector2(x, y))) { + applyAtSymmetryPoints(x, y, symmetryType, (sx, sy) -> { + if (inBounds(sx, sy, size)) { + copyValue(x, y, sx, sy); + } + }); + } + }); + }); + } } public boolean inHalf(Vector2 pos, float angle) { @@ -652,10 +680,10 @@ protected U applyWithOffset(U other, BiIntObjConsumer action, int xOffset, in int smallerSize = StrictMath.min(size, otherSize); int biggerSize = StrictMath.max(size, otherSize); if (smallerSize == otherSize) { - Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, - otherSize, size); - Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, - otherSize, size); + Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, otherSize, + size); + Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, otherSize, + size); other.apply((x, y) -> { int shiftX = coordinateXMap.get(x); int shiftY = coordinateYMap.get(y); @@ -807,8 +835,12 @@ protected void assertSize(int size) { * * @return a copy of the mask */ + @SneakyThrows public U copy() { - return copy(getName() + COPY_NAME); + Class clazz = getClass(); + String maskName = getName() + COPY_NAME + copyCount++; + return (U) clazz.getDeclaredConstructor(clazz, String.class, boolean.class).newInstance(this, + maskName, false); } public U getFinalMask() { @@ -819,9 +851,15 @@ public U getFinalMask() { } @SneakyThrows - public U copy(String maskName) { + private U copy(String maskName) { + Class clazz = getClass(); + return (U) clazz.getDeclaredConstructor(clazz, String.class, boolean.class).newInstance(this, maskName, false); + } + + @SneakyThrows + public U immutableCopy() { Class clazz = getClass(); - return (U) clazz.getDeclaredConstructor(clazz, String.class).newInstance(this, maskName); + return (U) clazz.getDeclaredConstructor(clazz, String.class, boolean.class).newInstance(this, name, true); } public U startVisualDebugger() { diff --git a/shared/src/main/java/com/faforever/neroxis/mask/NormalMask.java b/shared/src/main/java/com/faforever/neroxis/mask/NormalMask.java index 8f1e9a667..5aef5494f 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/NormalMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/NormalMask.java @@ -9,7 +9,7 @@ import java.awt.image.WritableRaster; @SuppressWarnings({"UnusedReturnValue", "unused"}) -public final class NormalMask extends VectorMask { +public class NormalMask extends VectorMask { public NormalMask(int size, Long seed, SymmetrySettings symmetrySettings) { this(size, seed, null, false); } @@ -22,14 +22,6 @@ public NormalMask(int size, Long seed, SymmetrySettings symmetrySettings, String this(size, seed, name, false); } - public NormalMask(NormalMask other) { - this(other, null); - } - - public NormalMask(NormalMask other, String name) { - super(other, name); - } - public NormalMask(FloatMask other) { this(other, 1f, null); } @@ -61,6 +53,10 @@ public NormalMask(BufferedImage sourceImage, Long seed, SymmetrySettings symmetr }); } + protected NormalMask(NormalMask other, String name, boolean immutable) { + super(other, name, immutable); + } + @Override protected Vector3 createValue(float scaleFactor, float... components) { assertMatchingDimension(components.length); diff --git a/shared/src/main/java/com/faforever/neroxis/mask/OperationsMask.java b/shared/src/main/java/com/faforever/neroxis/mask/OperationsMask.java index 3c4bd4345..20fb84a4c 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/OperationsMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/OperationsMask.java @@ -6,15 +6,13 @@ import com.faforever.neroxis.util.vector.Vector2; @SuppressWarnings({"unchecked", "UnusedReturnValue", "unused"}) -public abstract sealed class OperationsMask> extends Mask permits - ComparableMask, - VectorMask { +public abstract class OperationsMask> extends Mask { protected OperationsMask(int size, Long seed, SymmetrySettings symmetrySettings, String name, boolean parallel) { super(size, seed, symmetrySettings, name, parallel); } - protected OperationsMask(U other, String name) { - super(other, name); + protected OperationsMask(U other, String name, boolean immutable) { + super(other, name, immutable); } public abstract T getSum(); diff --git a/shared/src/main/java/com/faforever/neroxis/mask/PrimitiveMask.java b/shared/src/main/java/com/faforever/neroxis/mask/PrimitiveMask.java index f247b8c7b..322c40c22 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/PrimitiveMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/PrimitiveMask.java @@ -3,13 +3,14 @@ import com.faforever.neroxis.map.SymmetrySettings; @SuppressWarnings({"UnusedReturnValue", "unused"}) -public abstract sealed class PrimitiveMask, U extends ComparableMask> extends ComparableMask permits BooleanMask, FloatMask, IntegerMask { +public abstract class PrimitiveMask, U extends ComparableMask> extends + ComparableMask { public PrimitiveMask(int size, Long seed, SymmetrySettings symmetrySettings, String name, boolean parallel) { super(size, seed, symmetrySettings, name, parallel); } - protected PrimitiveMask(U other, String name) { - super(other, name); + protected PrimitiveMask(U other, String name, boolean immutable) { + super(other, name, immutable); } protected abstract int[][] getInnerCount(); diff --git a/shared/src/main/java/com/faforever/neroxis/mask/Vector2Mask.java b/shared/src/main/java/com/faforever/neroxis/mask/Vector2Mask.java index 200a7e997..944f899ae 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/Vector2Mask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/Vector2Mask.java @@ -8,7 +8,7 @@ import java.util.Arrays; @SuppressWarnings({"UnusedReturnValue", "unused"}) -public final class Vector2Mask extends VectorMask { +public class Vector2Mask extends VectorMask { public Vector2Mask(int size, Long seed, SymmetrySettings symmetrySettings) { this(size, seed, symmetrySettings, null, false); } @@ -30,14 +30,6 @@ public Vector2Mask(int size, Long seed, SymmetrySettings symmetrySettings, Strin this(size, seed, symmetrySettings, name, false); } - public Vector2Mask(Vector2Mask other) { - this(other, null); - } - - public Vector2Mask(Vector2Mask other, String name) { - super(other, name); - } - public Vector2Mask(BufferedImage sourceImage, Long seed, SymmetrySettings symmetrySettings, float scaleFactor) { this(sourceImage, seed, symmetrySettings, scaleFactor, null, false); } @@ -52,6 +44,10 @@ public Vector2Mask(BufferedImage sourceImage, Long seed, SymmetrySettings symmet super(sourceImage, seed, symmetrySettings, scaleFactor, name, parallel); } + protected Vector2Mask(Vector2Mask other, String name, boolean immutable) { + super(other, name, immutable); + } + @Override protected Vector2 createValue(float scaleFactor, float... components) { assertMatchingDimension(components.length); diff --git a/shared/src/main/java/com/faforever/neroxis/mask/Vector3Mask.java b/shared/src/main/java/com/faforever/neroxis/mask/Vector3Mask.java index 9f0a8cbe8..304d005a7 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/Vector3Mask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/Vector3Mask.java @@ -7,7 +7,7 @@ import java.awt.image.WritableRaster; @SuppressWarnings({"UnusedReturnValue", "unused"}) -public final class Vector3Mask extends VectorMask { +public class Vector3Mask extends VectorMask { public Vector3Mask(int size, Long seed, SymmetrySettings symmetrySettings) { this(size, seed, symmetrySettings, null, false); } @@ -29,14 +29,6 @@ public Vector3Mask(int size, Long seed, SymmetrySettings symmetrySettings, Strin this(size, seed, symmetrySettings, name, false); } - public Vector3Mask(Vector3Mask other) { - this(other, null); - } - - public Vector3Mask(Vector3Mask other, String name) { - super(other, name); - } - public Vector3Mask(NormalMask other) { this(other, null); } @@ -63,6 +55,10 @@ public Vector3Mask(BufferedImage sourceImage, Long seed, SymmetrySettings symmet super(sourceImage, seed, symmetrySettings, scaleFactor, name, parallel); } + protected Vector3Mask(Vector3Mask other, String name, boolean immutable) { + super(other, name, immutable); + } + @Override protected Vector3 createValue(float scaleFactor, float... components) { assertMatchingDimension(components.length); diff --git a/shared/src/main/java/com/faforever/neroxis/mask/Vector4Mask.java b/shared/src/main/java/com/faforever/neroxis/mask/Vector4Mask.java index 8438eb403..5109a0e88 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/Vector4Mask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/Vector4Mask.java @@ -7,7 +7,7 @@ import java.awt.image.WritableRaster; @SuppressWarnings({"UnusedReturnValue", "unused"}) -public final class Vector4Mask extends VectorMask { +public class Vector4Mask extends VectorMask { public Vector4Mask(int size, Long seed, SymmetrySettings symmetrySettings) { this(size, seed, symmetrySettings, null, false); } @@ -29,14 +29,6 @@ public Vector4Mask(int size, Long seed, SymmetrySettings symmetrySettings, Strin this(size, seed, symmetrySettings, name, false); } - public Vector4Mask(Vector4Mask other) { - this(other, null); - } - - public Vector4Mask(Vector4Mask other, String name) { - super(other, name); - } - public Vector4Mask(BufferedImage sourceImage, Long seed, SymmetrySettings symmetrySettings, float scaleFactor) { this(sourceImage, seed, symmetrySettings, scaleFactor, null, false); } @@ -51,6 +43,10 @@ public Vector4Mask(BufferedImage sourceImage, Long seed, SymmetrySettings symmet super(sourceImage, seed, symmetrySettings, scaleFactor, name, parallel); } + protected Vector4Mask(Vector4Mask other, String name, boolean immutable) { + super(other, name, immutable); + } + @Override protected Vector4 createValue(float scaleFactor, float... components) { assertMatchingDimension(components.length); diff --git a/shared/src/main/java/com/faforever/neroxis/mask/VectorMask.java b/shared/src/main/java/com/faforever/neroxis/mask/VectorMask.java index 3c249aa95..40424a70a 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/VectorMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/VectorMask.java @@ -19,12 +19,8 @@ import java.util.Map; @SuppressWarnings({"unchecked", "UnusedReturnValue", "unused"}) -public abstract sealed class VectorMask, U extends VectorMask> extends - OperationsMask permits - NormalMask, - Vector2Mask, - Vector3Mask, - Vector4Mask { +public abstract class VectorMask, U extends VectorMask> extends + OperationsMask { protected T[][] mask; public VectorMask(BufferedImage sourceImage, Long seed, SymmetrySettings symmetrySettings, float scaleFactor, @@ -59,8 +55,8 @@ public VectorMask(Long seed, String name, FloatMask... components) { }, components); } - protected VectorMask(U other, String name) { - super(other, name); + protected VectorMask(U other, String name, boolean immutable) { + super(other, name, immutable); } protected void assertMatchingDimension(int numImageComponents) { @@ -168,8 +164,7 @@ protected U setSizeInternal(int newSize) { T[][] oldMask = mask; mask = getNullMask(newSize); Map coordinateMap = getSymmetricScalingCoordinateMap(oldSize, newSize); - setWithSymmetry(SymmetryType.SPAWN, - (x, y) -> oldMask[coordinateMap.get(x)][coordinateMap.get(y)].copy()); + set((x, y) -> oldMask[coordinateMap.get(x)][coordinateMap.get(y)].copy()); } }); } @@ -284,9 +279,9 @@ public U blurComponent(int radius, int component, BooleanMask other) { return enqueue(dependencies -> { BooleanMask limiter = (BooleanMask) dependencies.getFirst(); int[][] innerCount = getComponentInnerCount(component); - setComponent( - (x, y) -> limiter.get(x, y) ? calculateComponentAreaAverage(radius, x, y, innerCount) / 1000f : get( - x, y).get(component), component); + setComponent((x, y) -> limiter.get(x, y) ? + calculateComponentAreaAverage(radius, x, y, innerCount) / 1000f : get(x, y).get(component), + component); }, other); } @@ -635,8 +630,7 @@ public U setComponentWithOffset(FloatMask other, int component, int xOffset, int boolean wrapEdges) { return enqueue(dependencies -> { FloatMask source = (FloatMask) dependencies.getFirst(); - applyComponentWithOffset(source, this::setComponentAt, component, xOffset, yOffset, - center, wrapEdges); + applyComponentWithOffset(source, this::setComponentAt, component, xOffset, yOffset, center, wrapEdges); }, other); } @@ -644,8 +638,7 @@ public U addComponentWithOffset(FloatMask other, int component, int xOffset, int boolean wrapEdges) { return enqueue(dependencies -> { FloatMask source = (FloatMask) dependencies.getFirst(); - applyComponentWithOffset(source, this::addComponentAt, component, xOffset, yOffset, - center, wrapEdges); + applyComponentWithOffset(source, this::addComponentAt, component, xOffset, yOffset, center, wrapEdges); }, other); } @@ -653,8 +646,7 @@ public U subtractComponentWithOffset(FloatMask other, int component, int xOffset boolean wrapEdges) { return enqueue(dependencies -> { FloatMask source = (FloatMask) dependencies.getFirst(); - applyComponentWithOffset(source, this::subtractComponentAt, component, xOffset, - yOffset, center, wrapEdges); + applyComponentWithOffset(source, this::subtractComponentAt, component, xOffset, yOffset, center, wrapEdges); }, other); } @@ -662,8 +654,7 @@ public U multiplyComponentWithOffset(FloatMask other, int component, int xOffset boolean wrapEdges) { return enqueue(dependencies -> { FloatMask source = (FloatMask) dependencies.getFirst(); - applyComponentWithOffset(source, this::multiplyComponentAt, component, xOffset, - yOffset, center, wrapEdges); + applyComponentWithOffset(source, this::multiplyComponentAt, component, xOffset, yOffset, center, wrapEdges); }, other); } @@ -671,8 +662,7 @@ public U divideComponentWithOffset(FloatMask other, int component, int xOffset, boolean wrapEdges) { return enqueue(dependencies -> { FloatMask source = (FloatMask) dependencies.getFirst(); - applyComponentWithOffset(source, this::divideComponentAt, component, xOffset, - yOffset, center, wrapEdges); + applyComponentWithOffset(source, this::divideComponentAt, component, xOffset, yOffset, center, wrapEdges); }, other); } @@ -684,10 +674,10 @@ private U applyComponentWithOffset(FloatMask other, BiIntFloatIntConsumer action int smallerSize = StrictMath.min(size, otherSize); int biggerSize = StrictMath.max(size, otherSize); if (smallerSize == otherSize) { - Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, - otherSize, size); - Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, - otherSize, size); + Map coordinateXMap = getShiftedCoordinateMap(xOffset, center, wrapEdges, otherSize, + size); + Map coordinateYMap = getShiftedCoordinateMap(yOffset, center, wrapEdges, otherSize, + size); other.apply((x, y) -> { int shiftX = coordinateXMap.get(x); int shiftY = coordinateYMap.get(y); diff --git a/shared/src/main/java/com/faforever/neroxis/util/Pipeline.java b/shared/src/main/java/com/faforever/neroxis/util/Pipeline.java index 5ee050617..c6460363e 100644 --- a/shared/src/main/java/com/faforever/neroxis/util/Pipeline.java +++ b/shared/src/main/java/com/faforever/neroxis/util/Pipeline.java @@ -45,7 +45,7 @@ public static void add(Mask executingMask, List> maskDependenci String callingMethod = null; String callingLine = null; - if (DebugUtil.DEBUG) { + if (DebugUtil.DEBUG || DebugUtil.VISUALIZE) { callingMethod = DebugUtil.getLastStackTraceMethodInPackage("com.faforever.neroxis.mask"); callingLine = DebugUtil.getLastStackTraceLineAfterPackage("com.faforever.neroxis.mask"); } @@ -82,7 +82,7 @@ public static void add(Mask executingMask, List> maskDependenci executingMask.setVisualDebug(visualDebug); if ((DebugUtil.DEBUG && visualDebug) || (DebugUtil.VISUALIZE && - !executingMask.isMock())) { + !executingMask.isImmutable())) { VisualDebugger.visualizeMask(executingMask, finalCallingMethod, finalCallingLine); @@ -223,9 +223,9 @@ public Entry(int index, Mask executingMask, Collection dependencies this.methodName = method; this.line = line; this.future = future.thenRunAsync(() -> { - if (!executingMask.isMock() && dependants.stream() - .anyMatch(entry -> !entry.getExecutingMask() - .equals(executingMask))) { + if (!executingMask.isImmutable() && dependants.stream() + .anyMatch(entry -> !entry.getExecutingMask() + .equals(executingMask))) { immutableResult = executingMask.immutableCopy(); } else { immutableResult = executingMask; diff --git a/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java b/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java index 3034c7a7d..2b6d2846d 100644 --- a/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java +++ b/shared/src/main/java/com/faforever/neroxis/util/SymmetryUtil.java @@ -1,6 +1,7 @@ package com.faforever.neroxis.util; import com.faforever.neroxis.map.Symmetry; +import com.faforever.neroxis.util.functional.SymmetryRegionBoundsChecker; import com.faforever.neroxis.util.vector.Vector2; import java.util.ArrayList; @@ -9,6 +10,14 @@ public class SymmetryUtil { + public static SymmetryRegionBoundsChecker getSymmetryRegionBoundsChecker(Symmetry symmetry, int size) { + int maxXBound = getMaxXBound(symmetry, size); + IntUnaryOperator minYBoundFunction = getMinYBoundFunction(symmetry, size); + IntUnaryOperator maxYBoundFunction = getMaxYBoundFunction(symmetry, size); + return (x, y) -> x >= 0 && x < maxXBound && y >= minYBoundFunction.applyAsInt(x) + && y < maxYBoundFunction.applyAsInt(x); + } + public static int getMaxXBound(Symmetry symmetry, int size) { return switch (symmetry) { case POINT4, POINT5, POINT6, POINT7, POINT8, POINT9, POINT10, POINT11, POINT12, POINT13, POINT14, POINT15, diff --git a/shared/src/main/java/com/faforever/neroxis/util/functional/SymmetryRegionBoundsChecker.java b/shared/src/main/java/com/faforever/neroxis/util/functional/SymmetryRegionBoundsChecker.java new file mode 100644 index 000000000..bd7151c0d --- /dev/null +++ b/shared/src/main/java/com/faforever/neroxis/util/functional/SymmetryRegionBoundsChecker.java @@ -0,0 +1,13 @@ +package com.faforever.neroxis.util.functional; + +import com.faforever.neroxis.util.vector.Vector2; + +public interface SymmetryRegionBoundsChecker { + + boolean inBounds(int x, int y); + + default boolean inBounds(Vector2 location) { + return inBounds((int) location.getX(), (int) location.getY()); + } + +} diff --git a/shared/src/main/java/com/faforever/neroxis/util/functional/ToBooleanFunction.java b/shared/src/main/java/com/faforever/neroxis/util/functional/ToBooleanFunction.java deleted file mode 100644 index e4dbaca7f..000000000 --- a/shared/src/main/java/com/faforever/neroxis/util/functional/ToBooleanFunction.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.faforever.neroxis.util.functional; - -@FunctionalInterface -public interface ToBooleanFunction { - boolean apply(T value); -} diff --git a/shared/src/main/java/com/faforever/neroxis/util/vector/Vector.java b/shared/src/main/java/com/faforever/neroxis/util/vector/Vector.java index 67113ac78..dfd7fb3c6 100644 --- a/shared/src/main/java/com/faforever/neroxis/util/vector/Vector.java +++ b/shared/src/main/java/com/faforever/neroxis/util/vector/Vector.java @@ -1,11 +1,8 @@ package com.faforever.neroxis.util.vector; -import lombok.EqualsAndHashCode; - import java.util.Arrays; import java.util.Random; -@EqualsAndHashCode @SuppressWarnings("unchecked") public abstract class Vector> { public static final int X = 0; @@ -340,4 +337,21 @@ public String toString() { } return Arrays.toString(strings).replace("[", "").replace("]", ""); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Vector vector)) { + return false; + } + + return Arrays.equals(components, vector.components); + } + + @Override + public int hashCode() { + return Arrays.hashCode(components); + } } diff --git a/shared/src/main/java/com/faforever/neroxis/visualization/VisualDebugger.java b/shared/src/main/java/com/faforever/neroxis/visualization/VisualDebugger.java index 52949420c..074ee48cf 100644 --- a/shared/src/main/java/com/faforever/neroxis/visualization/VisualDebugger.java +++ b/shared/src/main/java/com/faforever/neroxis/visualization/VisualDebugger.java @@ -31,7 +31,7 @@ public static void visualizeMask(Mask mask, String method, String line) { SwingUtilities.invokeLater(() -> { createGui(); String name = copyOfmask.getVisualName(); - updateList(name + " " + method + " " + line, copyOfmask.immutableCopy()); + updateList(name, method, line, copyOfmask); } ); } @@ -100,7 +100,8 @@ private static void updateVisibleCanvas(MaskListItem maskListItem) { String maskName = maskListItem.maskName(); Mask mask = maskListItem.mask(); canvas.setMask(mask); - frame.setTitle(String.format("Mask: %s MaskSize: %d", maskName, mask.getSize())); + frame.setTitle(String.format("Mask: %s Method: %s Line %s MaskSize: %d", maskName, maskListItem.method(), + maskListItem.line(), mask.getSize())); } private static void setupCanvas() { @@ -116,9 +117,9 @@ private static void setupCanvas() { frame.add(canvas, constraints); } - private static void updateList(String uniqueMaskName, Mask mask) { - MASK_ITEMS_BY_NAME.computeIfAbsent(uniqueMaskName, ignored -> new ArrayList<>()) - .add(new MaskListItem(uniqueMaskName, mask)); + private static void updateList(String maskIdentifier, String method, String line, Mask mask) { + MASK_ITEMS_BY_NAME.computeIfAbsent(maskIdentifier, ignored -> new ArrayList<>()) + .add(new MaskListItem(maskIdentifier, method, line, mask)); refreshList(); } @@ -142,10 +143,10 @@ private static void refreshList() { } } - private record MaskListItem(String maskName, Mask mask) { + private record MaskListItem(String maskName, String method, String line, Mask mask) { @Override public String toString() { - return maskName; + return String.join(" ", maskName, method, line); } } } From 7d98fcbdcf14a87f28ae23b652e55a9e1d9e0568 Mon Sep 17 00:00:00 2001 From: Sheikah45 Date: Sun, 15 Sep 2024 13:16:37 -1000 Subject: [PATCH 6/8] Fix issues with pathing --- .../terrain/OneIslandTerrainGenerator.java | 4 ++-- .../faforever/neroxis/mask/ComparableMask.java | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/generator/src/main/java/com/faforever/neroxis/generator/terrain/OneIslandTerrainGenerator.java b/generator/src/main/java/com/faforever/neroxis/generator/terrain/OneIslandTerrainGenerator.java index ce70fc3e3..d77398fe1 100644 --- a/generator/src/main/java/com/faforever/neroxis/generator/terrain/OneIslandTerrainGenerator.java +++ b/generator/src/main/java/com/faforever/neroxis/generator/terrain/OneIslandTerrainGenerator.java @@ -56,9 +56,9 @@ protected void landSetup() { int minMiddlePoints = 2; int maxMiddlePoints = 4; int numTeamConnections = (int) (4 * landDensity + 4) / symmetrySettings.spawnSymmetry() - .getNumSymPoints(); + .getNumSymPoints(); int numTeammateConnections = (int) (2 * landDensity + 2) / symmetrySettings.spawnSymmetry() - .getNumSymPoints(); + .getNumSymPoints(); int numWalkers = (int) (8 * landDensity + 8) / symmetrySettings.spawnSymmetry().getNumSymPoints(); int bound = (int) (mapSize / 64 * (16 * (random.nextFloat() * .25f + (1 - landDensity) * .75f))) + mapSize / 8; diff --git a/shared/src/main/java/com/faforever/neroxis/mask/ComparableMask.java b/shared/src/main/java/com/faforever/neroxis/mask/ComparableMask.java index b6a9f2521..3ad526c13 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/ComparableMask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/ComparableMask.java @@ -66,15 +66,21 @@ && valueAtGreaterThanEqualTo(x + 1, y, && valueAtGreaterThanEqualTo( x, - y - + y + - 1, - value)) && - (y < - getSize() - - 1 && + value)) + && + (y + < + getSize() + - + 1 + && valueAtGreaterThanEqualTo( x, - y + + y + + 1, value)))); } From 5fd5b383a3b62d7c7436cf08dbcba5a67c34b78d Mon Sep 17 00:00:00 2001 From: Sheikah45 Date: Tue, 17 Sep 2024 14:18:59 -1000 Subject: [PATCH 7/8] Fix rebase issue --- .../java/com/faforever/neroxis/mask/Mask.java | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) 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 f2312f326..845bb9af1 100644 --- a/shared/src/main/java/com/faforever/neroxis/mask/Mask.java +++ b/shared/src/main/java/com/faforever/neroxis/mask/Mask.java @@ -19,6 +19,7 @@ import java.awt.image.BufferedImage; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; @@ -221,8 +222,10 @@ public U clear() { } protected U enqueue(Consumer>> function, Mask... usedMasks) { - assertMutable(); - List> dependencies = List.of(usedMasks); + if (immutable) { + throw new IllegalStateException("Mask is immutable and cannot be modified"); + } + List> dependencies = Arrays.asList(usedMasks); if (parallel && !Pipeline.isRunning()) { if (dependencies.stream().anyMatch(dep -> !dep.parallel)) { throw new IllegalArgumentException("Non parallel masks used as dependents"); @@ -233,8 +236,8 @@ protected U enqueue(Consumer>> function, Mask... usedMasks visible = false; function.accept(dependencies); visible = visibleState; - if (((DebugUtil.DEBUG && isVisualDebug()) || (DebugUtil.VISUALIZE && !isMock() && !isParallel())) && - visible) { + if (((DebugUtil.DEBUG && isVisualDebug()) || (DebugUtil.VISUALIZE && !isImmutable() && !isParallel())) + && visible) { String callingMethod = DebugUtil.getLastStackTraceMethodInPackage("com.faforever.neroxis.mask"); String callingLine = DebugUtil.getLastStackTraceLineAfterPackage("com.faforever.neroxis.mask"); VisualDebugger.visualizeMask(this, callingMethod, callingLine); @@ -291,31 +294,6 @@ protected static boolean inBounds(int x, int y, int size) { return x >= 0 && x < size && y >= 0 && y < size; } - protected U enqueue(Consumer>> function, Mask... usedMasks) { - if (immutable) { - throw new IllegalStateException("Mask is immutable and cannot be modified"); - } - List> dependencies = Arrays.asList(usedMasks); - if (parallel && !Pipeline.isRunning()) { - if (dependencies.stream().anyMatch(dep -> !dep.parallel)) { - throw new IllegalArgumentException("Non parallel masks used as dependents"); - } - Pipeline.add(this, dependencies, function); - } else { - boolean visibleState = visible; - visible = false; - function.accept(dependencies); - visible = visibleState; - if (((DebugUtil.DEBUG && isVisualDebug()) || (DebugUtil.VISUALIZE && !isImmutable() && !isParallel())) - && visible) { - String callingMethod = DebugUtil.getLastStackTraceMethodInPackage("com.faforever.neroxis.mask"); - String callingLine = DebugUtil.getLastStackTraceLineAfterPackage("com.faforever.neroxis.mask"); - VisualDebugger.visualizeMask(this, callingMethod, callingLine); - } - } - return (U) this; - } - protected int getMaxXBound(SymmetryType symmetryType) { Symmetry symmetry = symmetrySettings.getSymmetry(symmetryType); return SymmetryUtil.getMaxXBound(symmetry, getSize()); From 60755b2036d1766678ab0d4bb9c37b0414f48519 Mon Sep 17 00:00:00 2001 From: Sheikah45 Date: Tue, 17 Sep 2024 14:26:34 -1000 Subject: [PATCH 8/8] Fix tests --- .../neroxis/util/SymmetryUtilTest.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/shared/src/test/java/com/faforever/neroxis/util/SymmetryUtilTest.java b/shared/src/test/java/com/faforever/neroxis/util/SymmetryUtilTest.java index 1a31e61a8..69e0880fe 100644 --- a/shared/src/test/java/com/faforever/neroxis/util/SymmetryUtilTest.java +++ b/shared/src/test/java/com/faforever/neroxis/util/SymmetryUtilTest.java @@ -118,25 +118,25 @@ public void testMaxYBoundFunction() { assertEquals(size, SymmetryUtil.getMaxYBoundFunction(Symmetry.NONE, size).applyAsInt(testPoint)); assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT2, size).applyAsInt(testPoint)); assertEquals(size, SymmetryUtil.getMaxYBoundFunction(Symmetry.XZ, size).applyAsInt(testPoint)); - assertEquals(size, SymmetryUtil.getMaxYBoundFunction(Symmetry.X, size).applyAsInt(testPoint)); assertEquals(size - testPoint, SymmetryUtil.getMaxYBoundFunction(Symmetry.DIAG, size).applyAsInt(testPoint)); assertEquals(size - testPoint, SymmetryUtil.getMaxYBoundFunction(Symmetry.ZX, size).applyAsInt(testPoint)); assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.Z, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.QUAD, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT4, size).applyAsInt(testPoint)); assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT3, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT5, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT6, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT7, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT8, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT9, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT10, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT11, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT12, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT13, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT14, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT15, size).applyAsInt(testPoint)); - assertEquals(halfSize, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT16, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.X, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.QUAD, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT4, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT5, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT6, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT7, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT8, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT9, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT10, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT11, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT12, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT13, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT14, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT15, size).applyAsInt(testPoint)); + assertEquals(0, SymmetryUtil.getMaxYBoundFunction(Symmetry.POINT16, size).applyAsInt(testPoint)); int dx = 10; testPoint = halfSize + dx;