From c98596c25dd40d51c7d7e20a221777702f93e63c Mon Sep 17 00:00:00 2001 From: david Date: Sun, 16 Jun 2024 13:06:13 +0200 Subject: [PATCH 1/3] properly utilize streams --- .../gopaint/objects/brush/PaintBrush.java | 77 ++++++++++--------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/src/main/java/net/thenextlvl/gopaint/objects/brush/PaintBrush.java b/src/main/java/net/thenextlvl/gopaint/objects/brush/PaintBrush.java index 1aecb8e3..1dc5c57c 100644 --- a/src/main/java/net/thenextlvl/gopaint/objects/brush/PaintBrush.java +++ b/src/main/java/net/thenextlvl/gopaint/objects/brush/PaintBrush.java @@ -27,9 +27,9 @@ import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.entity.Player; +import org.bukkit.util.Vector; import java.util.*; -import java.util.stream.Stream; public class PaintBrush extends Brush { @@ -64,45 +64,46 @@ public void paint(Location target, Player player, BrushSettings brushSettings) { selectedPoints.remove(player.getUniqueId()); performEdit(player, session -> { + var world = player.getWorld(); Location first = locations.getFirst(); - Stream blocks = Sphere.getBlocksInRadius(first, brushSettings.size(), null, false); - blocks.forEach(block -> { - if (Height.getAverageHeightDiffAngle(block.getLocation(), 1) >= 0.1 - && Height.getAverageHeightDiffAngle(block.getLocation(), brushSettings.angleDistance()) - >= Math.tan(Math.toRadians(brushSettings.angleHeightDifference()))) { - return; - } - - double rate = (block.getLocation().distance(first) - (brushSettings.size() / 2.0) - * ((100.0 - brushSettings.falloffStrength()) / 100.0)) / ((brushSettings.size() / 2.0) - - (brushSettings.size() / 2.0) * ((100.0 - brushSettings.falloffStrength()) / 100.0)); - - if (brushSettings.random().nextDouble() <= rate) { - return; - } - - LinkedList newCurve = new LinkedList<>(); - newCurve.add(block.getLocation()); - for (Location location : locations) { - newCurve.add(block.getLocation().clone().add( - location.getX() - first.getX(), - location.getY() - first.getY(), - location.getZ() - first.getZ() - )); - } - BezierSpline spline = new BezierSpline(newCurve); - double maxCount = (spline.getCurveLength() * 2.5) + 1; - for (int y = 0; y <= maxCount; y++) { - Block point = spline.getPoint((y / maxCount) * (locations.size() - 1)).getBlock(); - - if (point.isEmpty() || !passesDefaultChecks(brushSettings, player, point)) { - continue; - } - - setBlock(session, point, brushSettings.randomBlock()); - } - }); + Sphere.getBlocksInRadius(first, brushSettings.size(), null, false) + .filter(block -> Height.getAverageHeightDiffAngle(block.getLocation(), 1) < 0.1 + || Height.getAverageHeightDiffAngle(block.getLocation(), brushSettings.angleDistance()) + < Math.tan(Math.toRadians(brushSettings.angleHeightDifference()))) + .filter(block -> { + var rate = calculateRate(block, first, brushSettings); + return brushSettings.random().nextDouble() > rate; + }).forEach(block -> { + var curve = new LinkedList(); + curve.add(new Vector(block.getX(), block.getY(), block.getZ())); + locations.stream().map(location -> new Vector( + block.getX() + location.getX() - first.getX(), + block.getY() + location.getY() - first.getY(), + block.getZ() + location.getZ() - first.getZ() + )).forEach(curve::add); + + var spline = new BezierSpline(curve); + var maxCount = (spline.getCurveLength() * 2.5) + 1; + + for (int y = 0; y <= maxCount; y++) { + var point = spline.getPoint((y / maxCount) * (locations.size() - 1)).toLocation(world).getBlock(); + + if (point.isEmpty() || !passesDefaultChecks(brushSettings, player, point)) { + continue; + } + + setBlock(session, point, brushSettings.randomBlock()); + } + }); }); } + private double calculateRate(Block block, Location first, BrushSettings brushSettings) { + double sizeHalf = brushSettings.size() / 2.0; + double falloffStrengthFactor = (100.0 - brushSettings.falloffStrength()) / 100.0; + double numerator = block.getLocation().distance(first) - sizeHalf * falloffStrengthFactor; + double denominator = sizeHalf - sizeHalf * falloffStrengthFactor; + + return numerator / denominator; + } } From 8a398457c2f04369b5119b4b556d865d73b0f7b4 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 16 Jun 2024 13:06:41 +0200 Subject: [PATCH 2/3] added nullability information --- .../thenextlvl/gopaint/utils/curve/package-info.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/net/thenextlvl/gopaint/utils/curve/package-info.java diff --git a/src/main/java/net/thenextlvl/gopaint/utils/curve/package-info.java b/src/main/java/net/thenextlvl/gopaint/utils/curve/package-info.java new file mode 100644 index 00000000..655d4b0d --- /dev/null +++ b/src/main/java/net/thenextlvl/gopaint/utils/curve/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.gopaint.utils.curve; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file From 5ca1dbf21f183473c6d43d584e240cf0f7c82eee Mon Sep 17 00:00:00 2001 From: david Date: Sun, 16 Jun 2024 13:08:10 +0200 Subject: [PATCH 3/3] use vectors clarified field, parameter and method names extracted side effects were possible cleanup --- .../gopaint/utils/curve/BezierSpline.java | 176 ++++++++---------- .../utils/curve/BezierSplineSegment.java | 107 ++++++----- 2 files changed, 138 insertions(+), 145 deletions(-) diff --git a/src/main/java/net/thenextlvl/gopaint/utils/curve/BezierSpline.java b/src/main/java/net/thenextlvl/gopaint/utils/curve/BezierSpline.java index d36eab59..b74a4e3f 100644 --- a/src/main/java/net/thenextlvl/gopaint/utils/curve/BezierSpline.java +++ b/src/main/java/net/thenextlvl/gopaint/utils/curve/BezierSpline.java @@ -18,45 +18,42 @@ */ package net.thenextlvl.gopaint.utils.curve; -import org.bukkit.Location; +import lombok.Getter; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Contract; +import java.util.Arrays; import java.util.LinkedList; +import java.util.OptionalDouble; +@Getter public class BezierSpline { - private final LinkedList knotsList; - private Location[] knots; - private BezierSplineSegment[] segments; - private double length = 0; + private final Vector[] knots; + private final BezierSplineSegment[] segments; + private final double curveLength; - public BezierSpline(LinkedList knotsList) { - this.knotsList = knotsList; - recalculate(); - } - - private void recalculate() { - knots = knotsList.toArray(new Location[0]); - segments = new BezierSplineSegment[knots.length - 1]; - for (int i = 0; i < knots.length - 1; i++) { - segments[i] = new BezierSplineSegment(knots[i], knots[i + 1]); + public BezierSpline(LinkedList curve) { + this.knots = curve.toArray(new Vector[0]); + this.segments = new BezierSplineSegment[knots.length - 1]; + for (var segment = 0; segment < knots.length - 1; segment++) { + segments[segment] = new BezierSplineSegment(knots[segment], knots[segment + 1]); } calculateControlPoints(); - calculateLength(); - } - - public double getCurveLength() { - return length; + this.curveLength = calculateLength(); } - public void calculateLength() { - length = 0; - for (BezierSplineSegment segment : segments) { - segment.calculateCurveLength(); + @Contract(pure = true) + public double calculateLength() { + var length = this.curveLength; + for (var segment : segments) { length += segment.getCurveLength(); } + return length; } - public Location getPoint(double point) { + @Contract(pure = true) + public Vector getPoint(double point) { if (point >= segments.length) { return getPoint(segments.length - 1, 1); } else { @@ -64,109 +61,92 @@ public Location getPoint(double point) { } } - public Location getPoint(int n, double f) { - assert (n < segments.length); - assert (0 <= f && f <= 1); - BezierSplineSegment segment = segments[n]; - return segment.getPoint(f); + @Contract(pure = true) + public Vector getPoint(int segmentIndex, double factor) { + assert (segmentIndex < segments.length); + assert (factor > 0 && factor <= 1); + return segments[segmentIndex].getPoint(factor); } public void calculateControlPoints() { - if (segments == null) return; if (segments.length == 0) return; - Double xflat, yflat, zflat; - xflat = knots[0].getX(); - yflat = knots[0].getY(); - zflat = knots[0].getZ(); - for (Location l : knots) { - if (l.getBlockX() != xflat) { - xflat = null; - break; - } - } - for (Location l : knots) { - if (l.getBlockY() != yflat) { - yflat = null; - break; - } - } - for (Location l : knots) { - if (l.getBlockZ() != zflat) { - zflat = null; - break; - } - } + var xFlat = Arrays.stream(knots).allMatch(l -> l.getBlockX() == knots[0].getX()) + ? OptionalDouble.of(knots[0].getX()) : OptionalDouble.empty(); + var yFlat = Arrays.stream(knots).allMatch(l -> l.getBlockY() == knots[0].getY()) + ? OptionalDouble.of(knots[0].getY()) : OptionalDouble.empty(); + var zFlat = Arrays.stream(knots).allMatch(l -> l.getBlockZ() == knots[0].getZ()) + ? OptionalDouble.of(knots[0].getZ()) : OptionalDouble.empty(); if (segments.length == 1) { - Location midpoint = new Location(segments[0].getP0().getWorld(), 0, 0, 0); - midpoint.setX((segments[0].getP0().getX() + segments[0].getP3().getX()) / 2); - midpoint.setY((segments[0].getP0().getY() + segments[0].getP3().getY()) / 2); - midpoint.setZ((segments[0].getP0().getZ() + segments[0].getP3().getZ()) / 2); - segments[0].setP1(midpoint); - segments[0].setP2(midpoint.clone()); + var midpoint = new Vector(0, 0, 0); + midpoint.setX((segments[0].getStartPoint().getX() + segments[0].getEndPoint().getX()) / 2); + midpoint.setY((segments[0].getStartPoint().getY() + segments[0].getEndPoint().getY()) / 2); + midpoint.setZ((segments[0].getStartPoint().getZ() + segments[0].getEndPoint().getZ()) / 2); + segments[0].setIntermediatePoint1(midpoint); + segments[0].setIntermediatePoint2(midpoint.clone()); } else { - segments[0].setA(0); - segments[0].setB(2); - segments[0].setC(1); - segments[0].getR().setX(knots[0].getX() + 2 * knots[1].getX()); - segments[0].getR().setY(knots[0].getY() + 2 * knots[1].getY()); - segments[0].getR().setZ(knots[0].getZ() + 2 * knots[1].getZ()); + segments[0].setCoefficient1(0); + segments[0].setCoefficient2(2); + segments[0].setCoefficient3(1); + segments[0].getResult().setX(knots[0].getX() + 2 * knots[1].getX()); + segments[0].getResult().setY(knots[0].getY() + 2 * knots[1].getY()); + segments[0].getResult().setZ(knots[0].getZ() + 2 * knots[1].getZ()); int n = knots.length - 1; float m; for (int i = 1; i < n - 1; i++) { - segments[i].setA(1); - segments[i].setB(4); - segments[i].setC(1); - segments[i].getR().setX(4 * knots[i].getX() + 2 * knots[i + 1].getX()); - segments[i].getR().setY(4 * knots[i].getY() + 2 * knots[i + 1].getY()); - segments[i].getR().setZ(4 * knots[i].getZ() + 2 * knots[i + 1].getZ()); + segments[i].setCoefficient1(1); + segments[i].setCoefficient2(4); + segments[i].setCoefficient3(1); + segments[i].getResult().setX(4 * knots[i].getX() + 2 * knots[i + 1].getX()); + segments[i].getResult().setY(4 * knots[i].getY() + 2 * knots[i + 1].getY()); + segments[i].getResult().setZ(4 * knots[i].getZ() + 2 * knots[i + 1].getZ()); } - segments[n - 1].setA(2); - segments[n - 1].setB(7); - segments[n - 1].setC(0); - segments[n - 1].getR().setX(8 * knots[n - 1].getX() + knots[n].getX()); - segments[n - 1].getR().setY(8 * knots[n - 1].getY() + knots[n].getY()); - segments[n - 1].getR().setZ(8 * knots[n - 1].getZ() + knots[n].getZ()); + segments[n - 1].setCoefficient1(2); + segments[n - 1].setCoefficient2(7); + segments[n - 1].setCoefficient3(0); + segments[n - 1].getResult().setX(8 * knots[n - 1].getX() + knots[n].getX()); + segments[n - 1].getResult().setY(8 * knots[n - 1].getY() + knots[n].getY()); + segments[n - 1].getResult().setZ(8 * knots[n - 1].getZ() + knots[n].getZ()); for (int i = 1; i < n; i++) { - m = segments[i].getA() / segments[i - 1].getB(); - segments[i].setB(segments[i].getB() - m * segments[i - 1].getC()); - segments[i].getR().setX(segments[i].getR().getX() - m * segments[i - 1].getR().getX()); - segments[i].getR().setY(segments[i].getR().getY() - m * segments[i - 1].getR().getY()); - segments[i].getR().setZ(segments[i].getR().getZ() - m * segments[i - 1].getR().getZ()); + m = segments[i].getCoefficient1() / segments[i - 1].getCoefficient2(); + segments[i].setCoefficient2(segments[i].getCoefficient2() - m * segments[i - 1].getCoefficient3()); + segments[i].getResult().setX(segments[i].getResult().getX() - m * segments[i - 1].getResult().getX()); + segments[i].getResult().setY(segments[i].getResult().getY() - m * segments[i - 1].getResult().getY()); + segments[i].getResult().setZ(segments[i].getResult().getZ() - m * segments[i - 1].getResult().getZ()); } - segments[n - 1].getP1().setX(segments[n - 1].getR().getX() / segments[n - 1].getB()); - segments[n - 1].getP1().setY(segments[n - 1].getR().getY() / segments[n - 1].getB()); - segments[n - 1].getP1().setZ(segments[n - 1].getR().getZ() / segments[n - 1].getB()); + segments[n - 1].getIntermediatePoint1().setX(segments[n - 1].getResult().getX() / segments[n - 1].getCoefficient2()); + segments[n - 1].getIntermediatePoint1().setY(segments[n - 1].getResult().getY() / segments[n - 1].getCoefficient2()); + segments[n - 1].getIntermediatePoint1().setZ(segments[n - 1].getResult().getZ() / segments[n - 1].getCoefficient2()); for (int i = n - 2; i >= 0; i--) { - segments[i].getP1().setX((segments[i].getR().getX() - segments[i].getC() * segments[i + 1].getP1().getX()) / segments[i].getB()); - segments[i].getP1().setY((segments[i].getR().getY() - segments[i].getC() * segments[i + 1].getP1().getY()) / segments[i].getB()); - segments[i].getP1().setZ((segments[i].getR().getZ() - segments[i].getC() * segments[i + 1].getP1().getZ()) / segments[i].getB()); + segments[i].getIntermediatePoint1().setX((segments[i].getResult().getX() - segments[i].getCoefficient3() * segments[i + 1].getIntermediatePoint1().getX()) / segments[i].getCoefficient2()); + segments[i].getIntermediatePoint1().setY((segments[i].getResult().getY() - segments[i].getCoefficient3() * segments[i + 1].getIntermediatePoint1().getY()) / segments[i].getCoefficient2()); + segments[i].getIntermediatePoint1().setZ((segments[i].getResult().getZ() - segments[i].getCoefficient3() * segments[i + 1].getIntermediatePoint1().getZ()) / segments[i].getCoefficient2()); } for (int i = 0; i < n - 1; i++) { - segments[i].getP2().setX(2 * knots[i + 1].getX() - segments[i + 1].getP1().getX()); - segments[i].getP2().setY(2 * knots[i + 1].getY() - segments[i + 1].getP1().getY()); - segments[i].getP2().setZ(2 * knots[i + 1].getZ() - segments[i + 1].getP1().getZ()); + segments[i].getIntermediatePoint2().setX(2 * knots[i + 1].getX() - segments[i + 1].getIntermediatePoint1().getX()); + segments[i].getIntermediatePoint2().setY(2 * knots[i + 1].getY() - segments[i + 1].getIntermediatePoint1().getY()); + segments[i].getIntermediatePoint2().setZ(2 * knots[i + 1].getZ() - segments[i + 1].getIntermediatePoint1().getZ()); } - segments[n - 1].getP2().setX(0.5 * (knots[n].getX() + segments[n - 1].getP1().getX())); - segments[n - 1].getP2().setY(0.5 * (knots[n].getY() + segments[n - 1].getP1().getY())); - segments[n - 1].getP2().setZ(0.5 * (knots[n].getZ() + segments[n - 1].getP1().getZ())); + segments[n - 1].getIntermediatePoint2().setX(0.5 * (knots[n].getX() + segments[n - 1].getIntermediatePoint1().getX())); + segments[n - 1].getIntermediatePoint2().setY(0.5 * (knots[n].getY() + segments[n - 1].getIntermediatePoint1().getY())); + segments[n - 1].getIntermediatePoint2().setZ(0.5 * (knots[n].getZ() + segments[n - 1].getIntermediatePoint1().getZ())); } - if (xflat != null) for (BezierSplineSegment cs : segments) cs.setX(xflat); - if (yflat != null) for (BezierSplineSegment cs : segments) cs.setY(yflat); - if (zflat != null) for (BezierSplineSegment cs : segments) cs.setZ(zflat); + xFlat.ifPresent(value -> Arrays.stream(segments).forEach(segment -> segment.setX(value))); + yFlat.ifPresent(value -> Arrays.stream(segments).forEach(segment -> segment.setY(value))); + zFlat.ifPresent(value -> Arrays.stream(segments).forEach(segment -> segment.setZ(value))); } @Override public String toString() { - return (knots != null ? knots.length : 0) + " points."; + return knots.length + " points."; } } diff --git a/src/main/java/net/thenextlvl/gopaint/utils/curve/BezierSplineSegment.java b/src/main/java/net/thenextlvl/gopaint/utils/curve/BezierSplineSegment.java index 0a8fa45e..5aac056b 100644 --- a/src/main/java/net/thenextlvl/gopaint/utils/curve/BezierSplineSegment.java +++ b/src/main/java/net/thenextlvl/gopaint/utils/curve/BezierSplineSegment.java @@ -19,73 +19,86 @@ package net.thenextlvl.gopaint.utils.curve; import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.Setter; -import org.bukkit.Location; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import java.util.Objects; @Getter @Setter +@RequiredArgsConstructor public class BezierSplineSegment { - private final double[] lengths = new double[20]; - private Location p0, p1, p2, p3; - private float a, b, c; + private final Vector startPoint; + private final Vector endPoint; + + private Vector intermediatePoint1 = new Vector(0, 0, 0); + private Vector intermediatePoint2 = new Vector(0, 0, 0); + + private float coefficient1; + private float coefficient2; + private float coefficient3; + private @Nullable Double xFlat, yFlat, zFlat; - private Location r; - private double curveLength; - public BezierSplineSegment(Location p0, Location p3) { - this.p0 = p0; - this.p3 = p3; - p1 = new Location(p0.getWorld(), 0, 0, 0); - p2 = new Location(p0.getWorld(), 0, 0, 0); - r = new Location(p0.getWorld(), 0, 0, 0); - } + private Vector result = new Vector(0, 0, 0); - public void setX(double xflat2) { - p0.setX(xflat2); - p1.setX(xflat2); - p2.setX(xflat2); - p3.setX(xflat2); - xFlat = xflat2; + public void setX(double xFlat) { + startPoint.setX(xFlat); + intermediatePoint1.setX(xFlat); + intermediatePoint2.setX(xFlat); + endPoint.setX(xFlat); + this.xFlat = xFlat; } - public void setY(double yflat2) { - p0.setY(yflat2); - p1.setY(yflat2); - p2.setY(yflat2); - p3.setY(yflat2); - yFlat = yflat2; + public void setY(double yFlat) { + startPoint.setY(yFlat); + intermediatePoint1.setY(yFlat); + intermediatePoint2.setY(yFlat); + endPoint.setY(yFlat); + this.yFlat = yFlat; } - public void setZ(double zflat2) { - p0.setZ(zflat2); - p1.setZ(zflat2); - p2.setZ(zflat2); - p3.setZ(zflat2); - zFlat = zflat2; + public void setZ(double zFlat) { + startPoint.setZ(zFlat); + intermediatePoint1.setZ(zFlat); + intermediatePoint2.setZ(zFlat); + endPoint.setZ(zFlat); + this.zFlat = zFlat; } - public void calculateCurveLength() { - Location current = p0.clone(); - double step = 0.05; - lengths[0] = 0; - Location temp; - for (int i = 1; i < 20; i++) { - temp = getPoint(i * step); - lengths[i] = lengths[i - 1] + temp.distance(current); - current = temp; + @Contract(pure = true) + public double getCurveLength() { + var current = startPoint.clone(); + var lengths = new double[20]; + for (int i = 1; i < lengths.length; i++) { + var point = getPoint(i * 0.05); + lengths[i] = lengths[i - 1] + point.distance(current); + current = point; } - curveLength = lengths[19]; + return lengths[lengths.length - 1]; + } + + @Contract(pure = true) + public Vector getPoint(double factor) { + var x = Objects.requireNonNullElseGet(xFlat, () -> calculatePoint( + factor, startPoint.getX(), intermediatePoint1.getX(), intermediatePoint2.getX(), endPoint.getX() + )); + var y = Objects.requireNonNullElseGet(yFlat, () -> calculatePoint( + factor, startPoint.getY(), intermediatePoint1.getY(), intermediatePoint2.getY(), endPoint.getY() + )); + var z = Objects.requireNonNullElseGet(zFlat, () -> calculatePoint( + factor, startPoint.getZ(), intermediatePoint1.getZ(), intermediatePoint2.getZ(), endPoint.getZ() + )); + return new Vector(x, y, z); } - public Location getPoint(double f) { - Location result = new Location(p0.getWorld(), 0, 0, 0); - result.setX(Objects.requireNonNullElseGet(xFlat, () -> (Math.pow(1 - f, 3) * p0.getX()) + (3 * Math.pow(1 - f, 2) * f * p1.getX()) + (3 * (1 - f) * f * f * p2.getX()) + (Math.pow(f, 3) * p3.getX()))); - result.setY(Objects.requireNonNullElseGet(yFlat, () -> (Math.pow(1 - f, 3) * p0.getY()) + (3 * Math.pow(1 - f, 2) * f * p1.getY()) + (3 * (1 - f) * f * f * p2.getY()) + (Math.pow(f, 3) * p3.getY()))); - result.setZ(Objects.requireNonNullElseGet(zFlat, () -> (Math.pow(1 - f, 3) * p0.getZ()) + (3 * Math.pow(1 - f, 2) * f * p1.getZ()) + (3 * (1 - f) * f * f * p2.getZ()) + (Math.pow(f, 3) * p3.getZ()))); - return result; + @Contract(pure = true) + private double calculatePoint(double factor, double startPoint, double intermediatePoint1, double intermediatePoint2, double endPoint) { + return (Math.pow(1 - factor, 3) * startPoint) + (3 * Math.pow(1 - factor, 2) * factor * intermediatePoint1) + + (3 * (1 - factor) * factor * factor * intermediatePoint2) + (Math.pow(factor, 3) * endPoint); } }