From 55d918681b08bff49e6ee2591a5b0b30940f8b4f Mon Sep 17 00:00:00 2001 From: lipian Date: Wed, 20 Nov 2019 22:08:34 -0800 Subject: [PATCH 1/4] Fixing three point circle brush --- .../brush/shape/ThreePointCircleBrush.java | 227 +++++++----------- 1 file changed, 90 insertions(+), 137 deletions(-) diff --git a/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java b/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java index 9d1d4b21..c9236d46 100644 --- a/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java +++ b/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java @@ -24,9 +24,13 @@ */ package com.thevoxelbox.voxelsniper.brush.shape; +import com.flowpowered.math.GenericMath; +import com.flowpowered.math.matrix.Matrix4d; import com.flowpowered.math.vector.Vector3d; +import com.flowpowered.math.vector.Vector4d; import com.thevoxelbox.voxelsniper.Message; import com.thevoxelbox.voxelsniper.SnipeData; +import com.thevoxelbox.voxelsniper.Undo; import com.thevoxelbox.voxelsniper.brush.Brush; import com.thevoxelbox.voxelsniper.brush.PerformBrush; import org.spongepowered.api.text.format.TextColors; @@ -42,7 +46,6 @@ public class ThreePointCircleBrush extends PerformBrush { private Vector3d coordsOne; private Vector3d coordsTwo; private Vector3d coordsThree; - private Tolerance tolerance = Tolerance.DEFAULT; /** * Default Constructor. @@ -53,16 +56,16 @@ public ThreePointCircleBrush() { @Override protected final void arrow(final SnipeData v) { if (this.coordsOne == null) { - this.coordsOne = this.targetBlock.getPosition().add(0.5, 0.5, 0.5); + this.coordsOne = this.targetBlock.getPosition(); //.add(0.5, 0.5, 0.5); v.sendMessage(TextColors.GRAY, "First Corner set."); } else if (this.coordsTwo == null) { - this.coordsTwo = this.targetBlock.getPosition().add(0.5, 0.5, 0.5); + this.coordsTwo = this.targetBlock.getPosition(); //.add(0.5, 0.5, 0.5); v.sendMessage(TextColors.GRAY, "Second Corner set."); } else if (this.coordsThree == null) { - this.coordsThree = this.targetBlock.getPosition().add(0.5, 0.5, 0.5); + this.coordsThree = this.targetBlock.getPosition(); //.add(0.5, 0.5, 0.5); v.sendMessage(TextColors.GRAY, "Third Corner set."); } else { - this.coordsOne = this.targetBlock.getPosition().add(0.5, 0.5, 0.5); + this.coordsOne = this.targetBlock.getPosition(); //.add(0.5, 0.5, 0.5); this.coordsTwo = null; this.coordsThree = null; v.sendMessage(TextColors.GRAY, "First Corner set."); @@ -72,153 +75,103 @@ protected final void arrow(final SnipeData v) { @Override protected final void powder(final SnipeData v) { if (this.coordsOne == null || this.coordsTwo == null || this.coordsThree == null) { + v.sendMessage(TextColors.RED, "ERROR: Set all points before creating the circle"); return; } - v.sendMessage(TextColors.RED, "Unfortunately the three-point circle is currently disabled as it does not work :("); + // Calculate triangle defining vectors -// final Vector3d vectorOne = this.coordsTwo.sub(this.coordsOne); -// final Vector3d vectorTwo = this.coordsThree.sub(this.coordsOne); -// final Vector3d vectorThree = this.coordsThree.sub(vectorTwo); -// -// // Redundant data check -// if (vectorOne.length() == 0 || vectorTwo.length() == 0 || vectorThree.length() == 0) { -// v.sendMessage(TextColors.RED, "ERROR: Invalid points, try again."); -// this.coordsOne = null; -// this.coordsTwo = null; -// this.coordsThree = null; -// return; -// } -// -// // Calculate normal vector of the plane. -// final Vector3d normalVector = vectorOne.cross(vectorTwo); -// -// // Calculate constant term of the plane. -// final double planeConstant = normalVector.getX() * this.coordsOne.getX() + normalVector.getY() * this.coordsOne.getY() -// + normalVector.getZ() * this.coordsOne.getZ(); -// -// final Vector3d midpointOne = this.coordsOne.add(this.coordsTwo.sub(this.coordsOne).mul(0.5)); -// final Vector3d midpointTwo = this.coordsOne.add(this.coordsThree.sub(this.coordsOne).mul(0.5)); -// -// // Find perpendicular vectors to two sides in the plane -// final Vector3d perpendicularOne = normalVector.cross(vectorOne); -// final Vector3d perpendicularTwo = normalVector.cross(vectorTwo); -// -// // determine value of parametric variable at intersection of two -// // perpendicular bisectors -// final Vector3d tNumerator = midpointTwo.sub(midpointOne).cross(perpendicularTwo); -// final Vector3d tDenominator = perpendicularOne.cross(perpendicularTwo); -// final double t = tNumerator.length() / tDenominator.length(); -// -// // Calculate Circumcenter and Brushcenter. -// final Vector3d circumcenter = perpendicularOne.mul(t); -// circumcenter.add(midpointOne); -// -// final Vector3d brushCenter = new Vector3d(Math.round(circumcenter.getX()), Math.round(circumcenter.getY()), Math.round(circumcenter.getZ())); -// -// // Calculate radius of circumcircle and determine brushsize -// final double radius = circumcenter.distance(this.coordsOne); -// final int brushSize = (int) (Math.ceil(radius) + 1); -// this.undo = new Undo((int) (4 * v.getBrushSize() * v.getBrushSize())); -// for (int x = -brushSize; x <= brushSize; x++) { -// for (int y = -brushSize; y <= brushSize; y++) { -// for (int z = -brushSize; z <= brushSize; z++) { -// // Calculate distance from center -// final double tempDistance = Math.pow(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2), .5); -// -// // gets corner-on blocks -// final double cornerConstant = normalVector.getX() * (circumcenter.getX() + x) + normalVector.getY() * (circumcenter.getY() + y) -// + normalVector.getZ() * (circumcenter.getZ() + z); -// -// // gets center-on blocks -// final double centerConstant = normalVector.getX() * (circumcenter.getX() + x + .5) -// + normalVector.getY() * (circumcenter.getY() + y + .5) + normalVector.getZ() * (circumcenter.getZ() + z + .5); -// -// // Check if point is within sphere and on plane (some -// // tolerance given) -// if (tempDistance <= radius && (Math.abs(cornerConstant - planeConstant) < this.tolerance.getValue() -// || Math.abs(centerConstant - planeConstant) < this.tolerance.getValue())) { -// perform(v, brushCenter.getFloorX() + x, brushCenter.getFloorY() + y, brushCenter.getFloorZ() + z); -// } -// -// } -// } -// } -// -// v.sendMessage(TextColors.GREEN, "Done."); -// v.owner().storeUndo(this.undo); -// this.undo = null; -// -// // Reset Brush -// this.coordsOne = null; -// this.coordsTwo = null; -// this.coordsThree = null; + final Vector3d delta1 = this.coordsTwo.sub(this.coordsOne); + final Vector3d delta2 = this.coordsThree.sub(this.coordsOne); + final Vector3d normalVector = delta1.cross(delta2); + + if (normalVector.lengthSquared() < GenericMath.DBL_EPSILON) { + v.sendMessage(TextColors.RED, "ERROR: Invalid points, try again."); + return; + } + + // Column vectors for the A matrix + Vector4d A1 = new Vector4d(this.coordsOne.getX(), this.coordsTwo.getX(), this.coordsThree.getX(), normalVector.getX()); + Vector4d A2 = new Vector4d(this.coordsOne.getY(), this.coordsTwo.getY(), this.coordsThree.getY(), normalVector.getY()); + Vector4d A3 = new Vector4d(this.coordsOne.getZ(), this.coordsTwo.getZ(), this.coordsThree.getZ(), normalVector.getZ()); + Vector4d A4 = new Vector4d(1, 1, 1, 0); + + double u1 = - this.coordsOne.lengthSquared(); + double u2 = - this.coordsTwo.lengthSquared(); + double u3 = - this.coordsThree.lengthSquared(); + + Vector4d b = new Vector4d(u1, u2, u3, -2 * normalVector.dot(this.coordsOne)); + + // Use Cramer's rule to calculate the center point + double detA = columnsToMatrix(A1, A2, A3, A4).determinant(); + double detA1 = columnsToMatrix(b, A2, A3, A4).determinant(); + double detA2 = columnsToMatrix(A1, b, A3, A4).determinant(); + double detA3 = columnsToMatrix(A1, A2, b, A4).determinant(); + + Vector3d center = new Vector3d( + (detA1 / detA) / -2, + (detA2 / detA) / -2, + (detA3 / detA) / -2 + ); + + double radius = center.distance(this.coordsOne); + + // Create two normal vectors on the plane that can act as the x and y unit vectors for drawing the circle + Vector3d xPrime = this.coordsOne.sub(center).normalize(); + Vector3d yPrime = normalVector.cross(xPrime).normalize(); + + + this.undo = new Undo((int) (2 * Math.PI * radius)); + + // Use the parametric description of a circle with respect to theta and r to determine where to place the + // next block + for (double r = 0; r <= radius; r += .9) { + double deltaTheta = .8 / r; + + for (double currentAngle = 0; currentAngle <= 2 * Math.PI * r; currentAngle += deltaTheta) { + Vector3d blockPos = xPrime.mul(Math.cos(currentAngle)) + .add(yPrime.mul(Math.sin(currentAngle))) + .mul(r).add(center); + + int x = (int) Math.round(blockPos.getX()); + int y = (int) Math.round(blockPos.getY()); + int z = (int) Math.round(blockPos.getZ()); + + perform(v, x, y, z); + } + } + + v.sendMessage(TextColors.GREEN, "Done."); + v.owner().storeUndo(this.undo); + this.undo = null; + // Reset Brush + this.coordsOne = null; + this.coordsTwo = null; + this.coordsThree = null; + + } + + private Matrix4d columnsToMatrix(Vector4d a, Vector4d b, Vector4d c, Vector4d d) { + return new Matrix4d( + a.getX(), b.getX(), c.getX(), d.getX(), + a.getY(), b.getY(), c.getY(), d.getY(), + a.getZ(), b.getZ(), c.getZ(), d.getZ(), + a.getW(), b.getW(), c.getW(), d.getW() + ); } @Override public final void info(final Message vm) { vm.brushName(this.info.name()); - switch (this.tolerance) { - case ACCURATE: - vm.custom(TextColors.GOLD, "Mode: Accurate"); - break; - case DEFAULT: - vm.custom(TextColors.GOLD, "Mode: Default"); - break; - case SMOOTH: - vm.custom(TextColors.GOLD, "Mode: Smooth"); - break; - default: - vm.custom(TextColors.GOLD, "Mode: Unknown"); - break; - } - } @Override public final void parameters(final String[] par, final SnipeData v) { - if (par.length > 0 && par[0].equalsIgnoreCase("info")) { + if (par.length == 1 && par[0].equalsIgnoreCase("info")) { v.sendMessage(TextColors.YELLOW, "3-Point Circle Brush instructions: Select three corners with the arrow brush, then generate the Circle with the powder brush."); - String toleranceOptions = ""; - for (final Tolerance tolerance : Tolerance.values()) { - if (!toleranceOptions.isEmpty()) { - toleranceOptions += "|"; - } - toleranceOptions += tolerance.name().toLowerCase(); - } - v.sendMessage(TextColors.GOLD, "/b tpc " + toleranceOptions + " -- Toggle the calculations to emphasize accuracy or smoothness"); - return; - } - - for (int i = 0; i < par.length; i++) { - final String parameter = par[i].toUpperCase(); - try { - this.tolerance = Tolerance.valueOf(parameter); - v.sendMessage(TextColors.AQUA, "Brush set to " + this.tolerance.name().toLowerCase() + " tolerance."); - return; - } catch (final IllegalArgumentException exception) { - v.getVoxelMessage().brushMessage("No such tolerance."); - } - } - } - - /** - * Enumeration on Tolerance values. - * - * @author MikeMatrix - */ - private enum Tolerance { - DEFAULT(1000), ACCURATE(10), SMOOTH(2000); - - private int value; - - Tolerance(final int value) { - this.value = value; - } - - public int getValue() { - return this.value; + } else { + v.sendMessage(TextColors.RED, "Do /b tpc info for information on this brush"); } } } From f0fc56d61cc9e8926c1efc014e153ba7f24eace5 Mon Sep 17 00:00:00 2001 From: lipian Date: Wed, 20 Nov 2019 22:21:53 -0800 Subject: [PATCH 2/4] Removed unneeded comments and cleaned up undo count --- .../voxelsniper/brush/shape/ThreePointCircleBrush.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java b/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java index c9236d46..10815f9b 100644 --- a/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java +++ b/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java @@ -56,16 +56,16 @@ public ThreePointCircleBrush() { @Override protected final void arrow(final SnipeData v) { if (this.coordsOne == null) { - this.coordsOne = this.targetBlock.getPosition(); //.add(0.5, 0.5, 0.5); + this.coordsOne = this.targetBlock.getPosition(); v.sendMessage(TextColors.GRAY, "First Corner set."); } else if (this.coordsTwo == null) { - this.coordsTwo = this.targetBlock.getPosition(); //.add(0.5, 0.5, 0.5); + this.coordsTwo = this.targetBlock.getPosition(); v.sendMessage(TextColors.GRAY, "Second Corner set."); } else if (this.coordsThree == null) { - this.coordsThree = this.targetBlock.getPosition(); //.add(0.5, 0.5, 0.5); + this.coordsThree = this.targetBlock.getPosition(); v.sendMessage(TextColors.GRAY, "Third Corner set."); } else { - this.coordsOne = this.targetBlock.getPosition(); //.add(0.5, 0.5, 0.5); + this.coordsOne = this.targetBlock.getPosition(); this.coordsTwo = null; this.coordsThree = null; v.sendMessage(TextColors.GRAY, "First Corner set."); @@ -120,7 +120,7 @@ protected final void powder(final SnipeData v) { Vector3d yPrime = normalVector.cross(xPrime).normalize(); - this.undo = new Undo((int) (2 * Math.PI * radius)); + this.undo = new Undo((int) (Math.PI * radius * radius)); // Use the parametric description of a circle with respect to theta and r to determine where to place the // next block From 0779763f9387da4e00ec87de73735a389ec1d5c1 Mon Sep 17 00:00:00 2001 From: lipian Date: Thu, 21 Nov 2019 13:22:55 -0800 Subject: [PATCH 3/4] Adding better comments --- .../brush/shape/ThreePointCircleBrush.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java b/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java index 10815f9b..b79b4364 100644 --- a/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java +++ b/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java @@ -89,7 +89,25 @@ protected final void powder(final SnipeData v) { return; } - // Column vectors for the A matrix + // Column vectors for the A matrix since we are trying to solve the equation + // Ax = b where A is: + // | x1, y1, z1, 1 | + // | x2, y2, z2, 1 | + // | x3, y3, z3, 1 | + // | Nx, Ny, Nz, 0 | + // Where xi, yi, zi represents the x, y, or z component of the ith coord and + // Nx, Ny, and Nz are the components of the normal vector + // and b is: + // | -x1^2 - y1^2 - z1^2 | + // | -x2^2 - y2^2 - z2^2 | + // | -x3^2 - y3^2 - z3^2 | + // | -2 * N * coordsOne | + // + // The first three equations come from the equation of a sphere centered on the three + // points we get while the fourth ensures the point is on the same plane as the other + // three points in the circle. This method is loosely based on method 1 from + // https://www.qc.edu.hk/math/Advanced%20Level/circle%20given%203%20points.htm but has + // been adapted for 3D. Vector4d A1 = new Vector4d(this.coordsOne.getX(), this.coordsTwo.getX(), this.coordsThree.getX(), normalVector.getX()); Vector4d A2 = new Vector4d(this.coordsOne.getY(), this.coordsTwo.getY(), this.coordsThree.getY(), normalVector.getY()); Vector4d A3 = new Vector4d(this.coordsOne.getZ(), this.coordsTwo.getZ(), this.coordsThree.getZ(), normalVector.getZ()); From ab0349d2d4e296afa4ab951d0ec2c2d5d40b25d8 Mon Sep 17 00:00:00 2001 From: lipian Date: Sat, 23 Nov 2019 22:35:21 -0800 Subject: [PATCH 4/4] Cleaning up instance variable for ThreePointCircleBrush.java --- .../brush/shape/ThreePointCircleBrush.java | 63 +++++++++---------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java b/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java index b79b4364..9f9a55d5 100644 --- a/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java +++ b/src/main/java/com/thevoxelbox/voxelsniper/brush/shape/ThreePointCircleBrush.java @@ -35,6 +35,9 @@ import com.thevoxelbox.voxelsniper.brush.PerformBrush; import org.spongepowered.api.text.format.TextColors; +import java.util.ArrayList; +import java.util.List; + @Brush.BrushInfo( name = "3-point Circle", aliases = {"tpc", "threepointcircle"}, @@ -43,46 +46,42 @@ ) public class ThreePointCircleBrush extends PerformBrush { - private Vector3d coordsOne; - private Vector3d coordsTwo; - private Vector3d coordsThree; + private List coords; + private static final String[] NUMBER_DESC = {"First", "Second", "Third"}; /** * Default Constructor. */ public ThreePointCircleBrush() { + coords = new ArrayList<>(); } @Override protected final void arrow(final SnipeData v) { - if (this.coordsOne == null) { - this.coordsOne = this.targetBlock.getPosition(); - v.sendMessage(TextColors.GRAY, "First Corner set."); - } else if (this.coordsTwo == null) { - this.coordsTwo = this.targetBlock.getPosition(); - v.sendMessage(TextColors.GRAY, "Second Corner set."); - } else if (this.coordsThree == null) { - this.coordsThree = this.targetBlock.getPosition(); - v.sendMessage(TextColors.GRAY, "Third Corner set."); - } else { - this.coordsOne = this.targetBlock.getPosition(); - this.coordsTwo = null; - this.coordsThree = null; - v.sendMessage(TextColors.GRAY, "First Corner set."); + if (this.coords.size() == 3) { + this.coords.clear(); } + + this.coords.add(this.targetBlock.getPosition()); + String message = NUMBER_DESC[coords.size() - 1] + " corner set."; + v.sendMessage(TextColors.GRAY, message); } @Override protected final void powder(final SnipeData v) { - if (this.coordsOne == null || this.coordsTwo == null || this.coordsThree == null) { + if (this.coords.size() != 3) { v.sendMessage(TextColors.RED, "ERROR: Set all points before creating the circle"); return; } + Vector3d coordOne = this.coords.get(0); + Vector3d coordTwo = this.coords.get(1); + Vector3d coordThree = this.coords.get(2); + // Calculate triangle defining vectors - final Vector3d delta1 = this.coordsTwo.sub(this.coordsOne); - final Vector3d delta2 = this.coordsThree.sub(this.coordsOne); - final Vector3d normalVector = delta1.cross(delta2); + Vector3d delta1 = coordTwo.sub(coordOne); + Vector3d delta2 = coordThree.sub(coordOne); + Vector3d normalVector = delta1.cross(delta2); if (normalVector.lengthSquared() < GenericMath.DBL_EPSILON) { v.sendMessage(TextColors.RED, "ERROR: Invalid points, try again."); @@ -108,16 +107,16 @@ protected final void powder(final SnipeData v) { // three points in the circle. This method is loosely based on method 1 from // https://www.qc.edu.hk/math/Advanced%20Level/circle%20given%203%20points.htm but has // been adapted for 3D. - Vector4d A1 = new Vector4d(this.coordsOne.getX(), this.coordsTwo.getX(), this.coordsThree.getX(), normalVector.getX()); - Vector4d A2 = new Vector4d(this.coordsOne.getY(), this.coordsTwo.getY(), this.coordsThree.getY(), normalVector.getY()); - Vector4d A3 = new Vector4d(this.coordsOne.getZ(), this.coordsTwo.getZ(), this.coordsThree.getZ(), normalVector.getZ()); + Vector4d A1 = new Vector4d(coordOne.getX(), coordTwo.getX(), coordThree.getX(), normalVector.getX()); + Vector4d A2 = new Vector4d(coordOne.getY(), coordTwo.getY(), coordThree.getY(), normalVector.getY()); + Vector4d A3 = new Vector4d(coordOne.getZ(), coordTwo.getZ(), coordThree.getZ(), normalVector.getZ()); Vector4d A4 = new Vector4d(1, 1, 1, 0); - double u1 = - this.coordsOne.lengthSquared(); - double u2 = - this.coordsTwo.lengthSquared(); - double u3 = - this.coordsThree.lengthSquared(); + double u1 = - coordOne.lengthSquared(); + double u2 = - coordTwo.lengthSquared(); + double u3 = - coordThree.lengthSquared(); - Vector4d b = new Vector4d(u1, u2, u3, -2 * normalVector.dot(this.coordsOne)); + Vector4d b = new Vector4d(u1, u2, u3, -2 * normalVector.dot(coordOne)); // Use Cramer's rule to calculate the center point double detA = columnsToMatrix(A1, A2, A3, A4).determinant(); @@ -131,10 +130,10 @@ protected final void powder(final SnipeData v) { (detA3 / detA) / -2 ); - double radius = center.distance(this.coordsOne); + double radius = center.distance(coordOne); // Create two normal vectors on the plane that can act as the x and y unit vectors for drawing the circle - Vector3d xPrime = this.coordsOne.sub(center).normalize(); + Vector3d xPrime = coordOne.sub(center).normalize(); Vector3d yPrime = normalVector.cross(xPrime).normalize(); @@ -163,9 +162,7 @@ protected final void powder(final SnipeData v) { this.undo = null; // Reset Brush - this.coordsOne = null; - this.coordsTwo = null; - this.coordsThree = null; + this.coords.clear(); }