From 68ff8f692174aa24ca7729e78aef1ee142056063 Mon Sep 17 00:00:00 2001 From: StevenRS11 Date: Tue, 19 Aug 2014 17:36:07 -0500 Subject: [PATCH] Added triangulation library and rift render Major change is addition of fractal rift rendering, currently first pass. Curves are registered and pregenerated in mod_pocketDim. Rifts look up these curves, choose one, rotate it, and render it. The render is a TESR that does stuff. Hard to explain, look at RenderRift in the code and look at the actual rifts in game to get an idea of what it does. I had to add a triangulation library to accomplish this. Will hopefully do something else that drag around all this. (I tried(and used comments)) --- .../mod_pocketDim/CommonProxy.java | 1 - .../mod_pocketDim/blocks/BlockRift.java | 6 +- .../mod_pocketDim/mod_pocketDim.java | 13 + .../saving/DimDataProcessor.java | 34 +- .../tileentities/TileEntityRift.java | 8 + .../mod_pocketDim/util/l_systems/LSystem.java | 501 +++++++ .../mod_pocketDimClient/ClientProxy.java | 3 +- .../mod_pocketDimClient/RenderRift.java | 169 +++ src/main/java/org/poly2tri/Poly2Tri.java | 124 ++ .../poly2tri/geometry/polygon/Polygon.java | 269 ++++ .../geometry/polygon/PolygonPoint.java | 39 + .../poly2tri/geometry/polygon/PolygonSet.java | 58 + .../geometry/polygon/PolygonUtil.java | 15 + .../poly2tri/geometry/primitives/Edge.java | 17 + .../poly2tri/geometry/primitives/Point.java | 31 + .../coordinate/AnyToXYTransform.java | 71 + .../coordinate/CoordinateTransform.java | 12 + .../coordinate/Matrix3Transform.java | 38 + .../transform/coordinate/NoTransform.java | 21 + .../coordinate/XYToAnyTransform.java | 74 + .../triangulation/Triangulatable.java | 22 + .../triangulation/TriangulationAlgorithm.java | 36 + .../TriangulationConstraint.java | 55 + .../triangulation/TriangulationContext.java | 171 +++ .../TriangulationDebugContext.java | 13 + .../triangulation/TriangulationMode.java | 6 + .../triangulation/TriangulationPoint.java | 112 ++ .../triangulation/TriangulationProcess.java | 341 +++++ .../TriangulationProcessEvent.java | 36 + .../TriangulationProcessListener.java | 36 + .../triangulation/TriangulationUtil.java | 213 +++ .../delaunay/DelaunayTriangle.java | 685 +++++++++ .../delaunay/sweep/AdvancingFront.java | 179 +++ .../delaunay/sweep/AdvancingFrontIndex.java | 43 + .../delaunay/sweep/AdvancingFrontNode.java | 84 ++ .../triangulation/delaunay/sweep/DTSweep.java | 1290 +++++++++++++++++ .../delaunay/sweep/DTSweepConstraint.java | 103 ++ .../delaunay/sweep/DTSweepContext.java | 280 ++++ .../delaunay/sweep/DTSweepDebugContext.java | 105 ++ .../sweep/DTSweepPointComparator.java | 35 + .../delaunay/sweep/PointOnEdgeException.java | 15 + .../triangulation/point/FloatBufferPoint.java | 94 ++ .../poly2tri/triangulation/point/TPoint.java | 69 + .../sets/ConstrainedPointSet.java | 121 ++ .../poly2tri/triangulation/sets/PointSet.java | 95 ++ .../triangulation/util/PointGenerator.java | 38 + .../triangulation/util/PolygonGenerator.java | 94 ++ .../util/QuadTreeRefinement.java | 17 + .../poly2tri/triangulation/util/Tuple2.java | 43 + .../poly2tri/triangulation/util/Tuple3.java | 45 + 50 files changed, 5967 insertions(+), 13 deletions(-) create mode 100644 src/main/java/StevenDimDoors/mod_pocketDim/util/l_systems/LSystem.java create mode 100644 src/main/java/StevenDimDoors/mod_pocketDimClient/RenderRift.java create mode 100644 src/main/java/org/poly2tri/Poly2Tri.java create mode 100644 src/main/java/org/poly2tri/geometry/polygon/Polygon.java create mode 100644 src/main/java/org/poly2tri/geometry/polygon/PolygonPoint.java create mode 100644 src/main/java/org/poly2tri/geometry/polygon/PolygonSet.java create mode 100644 src/main/java/org/poly2tri/geometry/polygon/PolygonUtil.java create mode 100644 src/main/java/org/poly2tri/geometry/primitives/Edge.java create mode 100644 src/main/java/org/poly2tri/geometry/primitives/Point.java create mode 100644 src/main/java/org/poly2tri/transform/coordinate/AnyToXYTransform.java create mode 100644 src/main/java/org/poly2tri/transform/coordinate/CoordinateTransform.java create mode 100644 src/main/java/org/poly2tri/transform/coordinate/Matrix3Transform.java create mode 100644 src/main/java/org/poly2tri/transform/coordinate/NoTransform.java create mode 100644 src/main/java/org/poly2tri/transform/coordinate/XYToAnyTransform.java create mode 100644 src/main/java/org/poly2tri/triangulation/Triangulatable.java create mode 100644 src/main/java/org/poly2tri/triangulation/TriangulationAlgorithm.java create mode 100644 src/main/java/org/poly2tri/triangulation/TriangulationConstraint.java create mode 100644 src/main/java/org/poly2tri/triangulation/TriangulationContext.java create mode 100644 src/main/java/org/poly2tri/triangulation/TriangulationDebugContext.java create mode 100644 src/main/java/org/poly2tri/triangulation/TriangulationMode.java create mode 100644 src/main/java/org/poly2tri/triangulation/TriangulationPoint.java create mode 100644 src/main/java/org/poly2tri/triangulation/TriangulationProcess.java create mode 100644 src/main/java/org/poly2tri/triangulation/TriangulationProcessEvent.java create mode 100644 src/main/java/org/poly2tri/triangulation/TriangulationProcessListener.java create mode 100644 src/main/java/org/poly2tri/triangulation/TriangulationUtil.java create mode 100644 src/main/java/org/poly2tri/triangulation/delaunay/DelaunayTriangle.java create mode 100644 src/main/java/org/poly2tri/triangulation/delaunay/sweep/AdvancingFront.java create mode 100644 src/main/java/org/poly2tri/triangulation/delaunay/sweep/AdvancingFrontIndex.java create mode 100644 src/main/java/org/poly2tri/triangulation/delaunay/sweep/AdvancingFrontNode.java create mode 100644 src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweep.java create mode 100644 src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepConstraint.java create mode 100644 src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepContext.java create mode 100644 src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepDebugContext.java create mode 100644 src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepPointComparator.java create mode 100644 src/main/java/org/poly2tri/triangulation/delaunay/sweep/PointOnEdgeException.java create mode 100644 src/main/java/org/poly2tri/triangulation/point/FloatBufferPoint.java create mode 100644 src/main/java/org/poly2tri/triangulation/point/TPoint.java create mode 100644 src/main/java/org/poly2tri/triangulation/sets/ConstrainedPointSet.java create mode 100644 src/main/java/org/poly2tri/triangulation/sets/PointSet.java create mode 100644 src/main/java/org/poly2tri/triangulation/util/PointGenerator.java create mode 100644 src/main/java/org/poly2tri/triangulation/util/PolygonGenerator.java create mode 100644 src/main/java/org/poly2tri/triangulation/util/QuadTreeRefinement.java create mode 100644 src/main/java/org/poly2tri/triangulation/util/Tuple2.java create mode 100644 src/main/java/org/poly2tri/triangulation/util/Tuple3.java diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/CommonProxy.java b/src/main/java/StevenDimDoors/mod_pocketDim/CommonProxy.java index 952b796..5f48227 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/CommonProxy.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/CommonProxy.java @@ -21,7 +21,6 @@ public class CommonProxy implements IGuiHandler public static String WARP_PNG = "/WARP.png"; public void registerRenderers() - { } public void registerEntity(Class entity, String entityname, int id, Object mod, int trackingrange, int updateFreq, boolean updatevelo) diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/blocks/BlockRift.java b/src/main/java/StevenDimDoors/mod_pocketDim/blocks/BlockRift.java index 987e0c5..3a314c7 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/blocks/BlockRift.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/blocks/BlockRift.java @@ -396,14 +396,14 @@ public class BlockRift extends Block implements ITileEntityProvider - FMLClientHandler.instance().getClient().effectRenderer.addEffect(new RiftFX(par1World,par2+.5+xChange+Xoffset*rand.nextGaussian(), par3+.5+yChange+Yoffset*rand.nextGaussian() , par4+.5+zChange+Zoffset*rand.nextGaussian(), rand.nextGaussian() * 0.001D, rand.nextGaussian() * 0.001D, rand.nextGaussian() * 0.001D, FMLClientHandler.instance().getClient().effectRenderer)); - FMLClientHandler.instance().getClient().effectRenderer.addEffect(new RiftFX(par1World,par2+.5-xChange-Xoffset*rand.nextGaussian(), par3+.5-yChange-Yoffset*rand.nextGaussian() , par4+.5-zChange-Zoffset*rand.nextGaussian(), rand.nextGaussian() * 0.001D, rand.nextGaussian() * 0.001D, rand.nextGaussian() * 0.001D, FMLClientHandler.instance().getClient().effectRenderer)); + //FMLClientHandler.instance().getClient().effectRenderer.addEffect(new RiftFX(par1World,par2+.5+xChange+Xoffset*rand.nextGaussian(), par3+.5+yChange+Yoffset*rand.nextGaussian() , par4+.5+zChange+Zoffset*rand.nextGaussian(), rand.nextGaussian() * 0.001D, rand.nextGaussian() * 0.001D, rand.nextGaussian() * 0.001D, FMLClientHandler.instance().getClient().effectRenderer)); + // FMLClientHandler.instance().getClient().effectRenderer.addEffect(new RiftFX(par1World,par2+.5-xChange-Xoffset*rand.nextGaussian(), par3+.5-yChange-Yoffset*rand.nextGaussian() , par4+.5-zChange-Zoffset*rand.nextGaussian(), rand.nextGaussian() * 0.001D, rand.nextGaussian() * 0.001D, rand.nextGaussian() * 0.001D, FMLClientHandler.instance().getClient().effectRenderer)); if(rand.nextBoolean()) { //renders an extra little blob on top of the actual rift location so its easier to find. Eventually will only render if the player has the goggles. - FMLClientHandler.instance().getClient().effectRenderer.addEffect(new GoggleRiftFX(par1World,par2+.5, par3+.5, par4+.5, rand.nextGaussian() * 0.01D, rand.nextGaussian() * 0.01D, rand.nextGaussian() * 0.01D, FMLClientHandler.instance().getClient().effectRenderer)); + // FMLClientHandler.instance().getClient().effectRenderer.addEffect(new GoggleRiftFX(par1World,par2+.5, par3+.5, par4+.5, rand.nextGaussian() * 0.01D, rand.nextGaussian() * 0.01D, rand.nextGaussian() * 0.01D, FMLClientHandler.instance().getClient().effectRenderer)); } if(tile.shouldClose) { diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/mod_pocketDim.java b/src/main/java/StevenDimDoors/mod_pocketDim/mod_pocketDim.java index c22b4c2..20bc9fb 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/mod_pocketDim.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/mod_pocketDim.java @@ -65,6 +65,7 @@ import StevenDimDoors.mod_pocketDim.tileentities.TileEntityDimDoor; import StevenDimDoors.mod_pocketDim.tileentities.TileEntityDimDoorGold; import StevenDimDoors.mod_pocketDim.tileentities.TileEntityRift; import StevenDimDoors.mod_pocketDim.tileentities.TileEntityTransTrapdoor; +import StevenDimDoors.mod_pocketDim.util.l_systems.LSystem; import StevenDimDoors.mod_pocketDim.world.BiomeGenLimbo; import StevenDimDoors.mod_pocketDim.world.BiomeGenPocket; import StevenDimDoors.mod_pocketDim.world.DDBiomeGenBase; @@ -325,6 +326,18 @@ public class mod_pocketDim DDLoot.registerInfo(properties); proxy.loadTextures(); proxy.registerRenderers(); + + + LSystem.generateLSystem("terdragon 9", LSystem.TERDRAGON, 9); + LSystem.generateLSystem("terdragon 8", LSystem.TERDRAGON, 8); + LSystem.generateLSystem("terdragon 7", LSystem.TERDRAGON, 7); + LSystem.generateLSystem("terdragon 6", LSystem.TERDRAGON, 6); + LSystem.generateLSystem("terdragon 5", LSystem.TERDRAGON, 5); + LSystem.generateLSystem("terdragon 4", LSystem.TERDRAGON, 4); + + LSystem.generateLSystem("dragon 15", LSystem.DRAGON, 15); + + } @EventHandler diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/saving/DimDataProcessor.java b/src/main/java/StevenDimDoors/mod_pocketDim/saving/DimDataProcessor.java index d42b8ef..1c6f248 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/saving/DimDataProcessor.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/saving/DimDataProcessor.java @@ -18,14 +18,21 @@ import com.google.gson.stream.JsonReader; public class DimDataProcessor extends BaseConfigurationProcessor { + //The name of the version ID where it is stored in the JSON public final String JSON_VERSION_PROPERTY_NAME = "SAVE_DATA_VERSION_ID_INSTANCE"; + + //mapping of version IDs to their corresponding schema. Prevents reloading of schema during save/load cycles private HashMap SAVE_DATA_SCHEMA; + + //The parser used to read in the JSON Files private static final JsonParser jsonParser = new JsonParser(); + //The directory for JSON schema files public static final String BASE_SCHEMA_PATH = "/assets/dimdoors/text/"; - - //TODO dont load the schemas every time + /** + * Need to manually include a schema defintion for every save file version currently supported + */ public DimDataProcessor() { SAVE_DATA_SCHEMA = new HashMap(); @@ -33,7 +40,7 @@ public class DimDataProcessor extends BaseConfigurationProcessor //Load the old schema/s SAVE_DATA_SCHEMA.put(982405775, loadSchema(BASE_SCHEMA_PATH+"Dim_Data_Schema_v982405775.json")); - //load the current schema + //load the schema representing the current save data format SAVE_DATA_SCHEMA.put(PackedDimData.SAVE_DATA_VERSION_ID, loadSchema(BASE_SCHEMA_PATH+"Dim_Data_Schema_v1-0-0.json")); } @@ -159,7 +166,18 @@ public class DimDataProcessor extends BaseConfigurationProcessor */ public JsonObject processSaveData(JsonObject schema, JsonObject save) { - if(save.get(JSON_VERSION_PROPERTY_NAME).getAsInt()== 982405775) + int incomingSaveVersionID = save.get(JSON_VERSION_PROPERTY_NAME).getAsInt(); + + // Handle save data versions that are current + if(incomingSaveVersionID == PackedDimData.SAVE_DATA_VERSION_ID) + { + JSONValidator.validate(this.getSaveDataSchema(save), save); + return save; + } + + // Handle save data versions that are older, starting with the random one. + // We have to + if(incomingSaveVersionID== 982405775) { DimensionType type; @@ -183,11 +201,11 @@ public class DimDataProcessor extends BaseConfigurationProcessor save.remove("IsDungeon"); save.addProperty("DimensionType",type.index); save.remove(this.JSON_VERSION_PROPERTY_NAME); - save.addProperty(this.JSON_VERSION_PROPERTY_NAME, PackedDimData.SAVE_DATA_VERSION_ID); - return processSaveData(this.getSaveDataSchema(save), save); + + //Need to hardcode the version number here, so if we change the current version then this still updates to the proper version + save.addProperty(this.JSON_VERSION_PROPERTY_NAME, 100); } - JSONValidator.validate(this.getSaveDataSchema(save), save); - return save; + return processSaveData(this.getSaveDataSchema(save), save); } } diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/tileentities/TileEntityRift.java b/src/main/java/StevenDimDoors/mod_pocketDim/tileentities/TileEntityRift.java index db31843..c7548f7 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDim/tileentities/TileEntityRift.java +++ b/src/main/java/StevenDimDoors/mod_pocketDim/tileentities/TileEntityRift.java @@ -44,12 +44,15 @@ public class TileEntityRift extends DDTileEntityBase public boolean shouldClose = false; public Point4D nearestRiftLocation = null; public int spawnedEndermenID = 0; + public int riftRotation; public TileEntityRift() { // Vary the update times of rifts to prevent all the rifts in a cluster // from updating at the same time. updateTimer = random.nextInt(UPDATE_PERIOD); + this.riftRotation = random.nextInt(360); + } @Override @@ -74,6 +77,7 @@ public class TileEntityRift extends DDTileEntityBase return; } + // Check if this rift should render white closing particles and // spread the closing effect to other rifts nearby. if (shouldClose) @@ -251,6 +255,8 @@ public class TileEntityRift extends DDTileEntityBase this.zOffset = nbt.getInteger("zOffset"); this.shouldClose = nbt.getBoolean("shouldClose"); this.spawnedEndermenID = nbt.getInteger("spawnedEndermenID"); + this.riftRotation = nbt.getInteger("riftRotation"); + } @Override @@ -263,6 +269,8 @@ public class TileEntityRift extends DDTileEntityBase nbt.setInteger("zOffset", this.zOffset); nbt.setBoolean("shouldClose", this.shouldClose); nbt.setInteger("spawnedEndermenID", this.spawnedEndermenID); + + nbt.setInteger("riftRotation", this.riftRotation); } @Override diff --git a/src/main/java/StevenDimDoors/mod_pocketDim/util/l_systems/LSystem.java b/src/main/java/StevenDimDoors/mod_pocketDim/util/l_systems/LSystem.java new file mode 100644 index 0000000..184fe33 --- /dev/null +++ b/src/main/java/StevenDimDoors/mod_pocketDim/util/l_systems/LSystem.java @@ -0,0 +1,501 @@ +package StevenDimDoors.mod_pocketDim.util.l_systems; + +import java.awt.Point; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import javax.swing.SwingUtilities; +import org.poly2tri.Poly2Tri; +import org.poly2tri.geometry.polygon.Polygon; +import org.poly2tri.geometry.polygon.PolygonPoint; +import org.poly2tri.triangulation.TriangulationPoint; +import org.poly2tri.triangulation.delaunay.DelaunayTriangle; + + +public class LSystem +{ + public static HashMap curves = new HashMap(); + + /** + * An array containing the args to generate a curve. + * index 0 = rules + * index 1 = angle + * index 2 = start string + */ + public static final String[] TERDRAGON = {"F>+F----F++++F-","60","F"}; + public static final String[] DRAGON = {"X>X+YF:Y>FX-Y","90","FX"}; + + + + /** + * Generates a fractal curve + * @param args: 0 = rules, 1 = angle, 2 = start + * @param steps + * @return + */ + public static void generateLSystem(String key, String[] args, int steps) + { + //Parse the rules from the first index + String[] rules = args[0].split(":"); + HashMap lSystemsRule = new HashMap(); + + for (String rule : rules) + { + String[] parts = rule.split(">"); + lSystemsRule.put(parts[0], parts[1]); + } + + //get the angle for each turn + int angle = Integer.parseInt(args[1]); + + + //String to hold the output + //Initialize with starting string + String output = args[2]; + + //generate the l-system + output = (generate(args[2], steps, lSystemsRule)); + + //get the boundary of the polygon + PolygonStorage polygon = getBoundary(convertToPoints(angle, output, (steps))); + + //replace the boundary of the polygon with a series of points representing triangles for rendering + polygon.points = tesselate(polygon); + + curves.put(key, polygon); + + } + + /** + * Takes an unordered list of points comprising a fractal curve and builds a + * closed polygon around it + * + * @param input + * @return + */ + public static PolygonStorage getBoundary(ArrayList input) + { + // store max x and y values to create bounding box + int maxY = Integer.MIN_VALUE; + int maxX = Integer.MIN_VALUE; + int minY = Integer.MAX_VALUE; + int minX = Integer.MAX_VALUE; + + // store confirmed duplicates here + HashSet duplicates = new HashSet(); + + // store possible singles here + HashSet singles = new HashSet(); + + // list to store confirmed singles and output in the correct order + ArrayList output = new ArrayList(); + + // sort into Hashmaps and hashsets to make contains operations possible, + // while testing for duplicates + for (double[] point : input) + { + // convert doubles to ints and record min/max values + + int xCoord = (int) Math.round(point[0]); + int yCoord = (int) Math.round(point[1]); + + if (xCoord > maxX) + { + maxX = xCoord; + } + else if (xCoord < minX) + { + minX = xCoord; + } + + if (yCoord > maxY) + { + maxY = yCoord; + } + else if (yCoord < minY) + { + minY = yCoord; + } + singles.add(new Point(xCoord, yCoord)); + + } + + // find a suitable starting point + Point startPoint = new Point(minX, minY); + Point prevPoint = (Point) startPoint.clone(); + + while (startPoint.y < maxY) + { + if (singles.contains(startPoint)) + { + break; + } + startPoint.y++; + } + + // record the first point so we know where to stop + final Point firstPoint = (Point) startPoint.clone(); + + // determine the direction to start searching from + Point direction = getVector(prevPoint, startPoint); + + //output.add(startPoint); + + // loop around in a clockwise circle, jumping to the next point when we + // find it and resetting the direction to start seaching from + // to the last found point. This ensures we always find the next + // *outside* point + do + { + // get the next point + direction = rotateCounterClockwise(direction); + Point target = new Point(startPoint.x + direction.x, startPoint.y + direction.y); + + // see if that point is part of our fractal curve + if (singles.contains(target)) + { + if(target.equals(firstPoint)) + { + output.remove(output.get(output.size()-1)); + break; + } + // get the vector to start from for the next cycle + direction = getVector(startPoint, target); + + // prune zero width spikes + if ((output.size() > 1 && output.get(output.size() - 2).equals(target))) + { + output.remove(output.size() - 1); + } + else + { + + if(output.contains(target)&&!target.equals(output.get(0))) + { + int index = output.indexOf(target); + while(output.size()>index) + { + output.remove(output.size()-1); + } + + } + output.add(target); + } + startPoint = target; + } + } + while (!(output.get(output.size() - 1).equals(firstPoint) && output.size() > 1) && output.size() < singles.size()); + + return new PolygonStorage(output, maxX, maxY, minX, minY); + } + + /** + * using a point as a 2d vector, normalize it (sorta) + * + * @param origin + * @param destination + * @return + */ + public static Point getVector(Point origin, Point destination) + { + int[] normals = { origin.x - destination.x, origin.y - destination.y }; + + for (int i = 0; i < normals.length; i++) + { + if (normals[i] > 0) + { + normals[i] = 1; + } + else if (normals[i] == 0) + { + normals[i] = 0; + } + else if (normals[i] < 0) + { + normals[i] = -1; + } + } + return new Point(normals[0], normals[1]); + } + + /** + * rotate a normal around the origin + * + * @param previous + * @return + */ + public static Point rotateCounterClockwise(Point previous) + { + Point point = new Point(); + + point.x = (int) (previous.x * Math.cos(Math.toRadians(90)) - previous.y * Math.sin(Math.toRadians(90))); + point.y = (int) (previous.x * Math.sin(Math.toRadians(90)) + previous.y * Math.cos(Math.toRadians(90))); + + return point; + } + + /** + * Take an l-system string and convert it into a series of points on a + * cartesian grid. Designed to keep terdragons oriented the same direction + * regardless of iterations + * + * @param angle + * @param system + * @param generations + * @return + */ + public static ArrayList convertToPoints(double angle, String system, int generations) + { + + // determine the starting point and rotation to begin drawing from + int rotation = (generations % 2) == 0 ? 2 : 4; + double[] currentState = { ((generations + rotation) % 4) * 90, 0, 0 }; + + // the output for a totally unordered list of points defining the curve + ArrayList output = new ArrayList(); + + // the stack used to deal with branching l-systems that use [ and ] + ArrayDeque state = new ArrayDeque(); + + // perform the rules corresponding to each symbol in the l-system + for (Character ch : system.toCharArray()) + { + double motion = 1; + + // move forward + if (ch == 'F') + { + currentState[1] -= (Math.cos(Math.toRadians(currentState[0])) * motion); + currentState[2] -= (Math.sin(Math.toRadians(currentState[0])) * motion); + output.add(new double[] { currentState[1], currentState[2] }); + + } + // start branch + if (ch == '[') + { + + state.push(currentState.clone()); + } + // turn left + if (ch == '-') + { + currentState = new double[] { (double) ((currentState[0] - angle) % 360), currentState[1], currentState[2] }; + } + // turn right + if (ch == '+') + { + currentState[0] = ((currentState[0] + angle) % 360); + + } + // end branch and return to previous fork + if (ch == ']') + { + currentState = state.pop(); + } + } + return output; + + } + + /** + * grow and l-system string based on the rules provided in the args + * + * @param start + * @param steps + * @param lSystemsRule + * @return + */ + public static String generate(String start, int steps, HashMap lSystemsRule) + { + + while (steps > 0) + { + StringBuilder output = new StringBuilder(); + + for (Character ch : start.toCharArray()) + { + // get the rule applicable for the variable + String data = lSystemsRule.get(ch.toString()); + + // handle constants for rule-less symbols + if (data == null) + { + data = ch.toString(); + } + output.append(data); + } + steps--; + start = output.toString(); + } + return start; + } + + // a data container class to transmit the important information about the polygon + public static class PolygonStorage + { + public PolygonStorage(ArrayList points, int maxX, int maxY, int minX, int minY) + { + this.points = points; + this.maxX = maxX; + this.maxY = maxY; + this.minX = minX; + this.minY = minY; + } + public ArrayList points; + + public int maxX; + public int maxY; + public int minX; + public int minY; + } + + + public static ArrayList tesselate(PolygonStorage polygon) + { + ArrayList points = new ArrayList(); + + ArrayList polyPoints = new ArrayList(); + + for(int i = 0; i tris =(ArrayList) poly.getTriangles(); + + for(DelaunayTriangle tri : tris) + { + for(TriangulationPoint tpoint : tri.points) + { + points.add(new Point((int)tpoint.getX(),(int) tpoint.getY())); + } + } + return points; + + } + + /** + public static ArrayList tesselate(Polygon polygon) + { + ArrayList points = new ArrayList(); + + Tessellator tess = new Tessellator(); + double[] verticesC1 = new double[polygon.points.size()*3]; + for(int i = 0; i< verticesC1.length; i+=3) + { + Point point = polygon.points.get(i/3); + verticesC1[i]= point.x; + verticesC1[i+1]= point.y; + verticesC1[i+2]= 0; + + } + + tess.gluBeginPolygon(); + + for(int i = 0; i vIndex = prim.vertices; + + if(prim.type==GL11.GL_TRIANGLE_STRIP) + { + for(Integer ii = 0; ii < vIndex.size()-1;ii++) + { + points.add(new Point((int)verticesC1[vIndex.get(ii)*3],(int) verticesC1[vIndex.get(ii)*3+1])); + points.add(new Point((int)verticesC1[vIndex.get(ii+1)*3],(int) verticesC1[vIndex.get(ii+1)*3+1])); + + + } + } + + if(prim.type==GL11.GL_TRIANGLES) + { + for(Integer ii = 0; ii < vIndex.size();ii++) + { + points.add(new Point((int)verticesC1[vIndex.get(ii)*3],(int) verticesC1[vIndex.get(ii)*3+1])); + } + } + + + + + + { + if(prim.type==GL11.GL_TRIANGLE_FAN) + { + Integer firstIndex = vIndex.get(0); + // points.add(new Point((int)verticesC1[vIndex.get(firstIndex)],(int) verticesC1[vIndex.get(firstIndex)+1])); + + Integer[] vertexList = new Integer[vIndex.size()*3]; + for(Integer ii = 0; ii < vIndex.size()-2;ii++) + { + vertexList[ii*3] = vIndex.get(0); + vertexList[ii*3+1] = vIndex.get(ii+1); + vertexList[ii*3+2] = vIndex.get(ii+2); + + + + + } + + for(Integer vertex : vertexList) + { + if(vertex!=null) + { + points.add(new Point((int)(verticesC1[vertex*3]),(int)(verticesC1[vertex*3+1]))); + + } + else + { + break; + } + } + System.out.println(vertexList); + + + } + + //points.add(new Point((int)verticesC1[vIndex.get(firstIndex)],(int) verticesC1[vIndex.get(firstIndex)+1])); + // points.add(new Point((int)verticesC1[vIndex.get(ii)*3],(int) verticesC1[vIndex.get(ii)+1])); + // points.add(new Point((int)verticesC1[vIndex.get(ii+1)*3],(int) verticesC1[vIndex.get(ii+1)*3+1])); + + // points.add(new Point((int)verticesC1[index],(int)verticesC1[index+1])); + // System.out.println(verticesC1[index]+","+verticesC1[index+1]+","+verticesC1[index+2]); + + + + } + //System.out.println(tess.primitives.get(i).toString()); + } + return points; + + } + **/ +} diff --git a/src/main/java/StevenDimDoors/mod_pocketDimClient/ClientProxy.java b/src/main/java/StevenDimDoors/mod_pocketDimClient/ClientProxy.java index 873aa8f..004e4ce 100644 --- a/src/main/java/StevenDimDoors/mod_pocketDimClient/ClientProxy.java +++ b/src/main/java/StevenDimDoors/mod_pocketDimClient/ClientProxy.java @@ -7,6 +7,7 @@ import StevenDimDoors.mod_pocketDim.core.DimLink; import StevenDimDoors.mod_pocketDim.core.PocketManager; import StevenDimDoors.mod_pocketDim.ticking.MobMonolith; import StevenDimDoors.mod_pocketDim.tileentities.TileEntityDimDoor; +import StevenDimDoors.mod_pocketDim.tileentities.TileEntityRift; import StevenDimDoors.mod_pocketDim.tileentities.TileEntityTransTrapdoor; import StevenDimDoors.mod_pocketDim.watcher.ClientLinkData; import cpw.mods.fml.client.registry.ClientRegistry; @@ -23,7 +24,7 @@ public class ClientProxy extends CommonProxy ClientRegistry.bindTileEntitySpecialRenderer(TileEntityDimDoor.class, new RenderDimDoor()); ClientRegistry.bindTileEntitySpecialRenderer(TileEntityTransTrapdoor.class, new RenderTransTrapdoor()); //This code activates the new rift rendering, as well as a bit of code in TileEntityRift - //ClientRegistry.bindTileEntitySpecialRenderer(TileEntityRift.class, new RenderRift()); + ClientRegistry.bindTileEntitySpecialRenderer(TileEntityRift.class, new RenderRift()); //MinecraftForgeClient.preloadTexture(RIFT2_PNG); RenderingRegistry.registerEntityRenderingHandler(MobMonolith.class, new RenderMobObelisk(.5F)); diff --git a/src/main/java/StevenDimDoors/mod_pocketDimClient/RenderRift.java b/src/main/java/StevenDimDoors/mod_pocketDimClient/RenderRift.java new file mode 100644 index 0000000..ea352c6 --- /dev/null +++ b/src/main/java/StevenDimDoors/mod_pocketDimClient/RenderRift.java @@ -0,0 +1,169 @@ +package StevenDimDoors.mod_pocketDimClient; + +import static org.lwjgl.opengl.GL11.*; +import java.awt.Point; +import java.util.HashMap; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; +import net.minecraft.tileentity.TileEntity; +import org.lwjgl.opengl.GL11; +import StevenDimDoors.mod_pocketDim.tileentities.TileEntityRift; +import StevenDimDoors.mod_pocketDim.util.l_systems.LSystem; +import StevenDimDoors.mod_pocketDim.util.l_systems.LSystem.PolygonStorage; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class RenderRift extends TileEntitySpecialRenderer +{ + + @Override + public void renderTileEntityAt(TileEntity te, double xWorld, double yWorld, double zWorld, float f) + { + // prepare fb for drawing + GL11.glPushMatrix(); + + // make the rift render on both sides, disable texture mapping and + // lighting + GL11.glDisable(GL11.GL_CULL_FACE); + GL11.glDisable(GL_TEXTURE_2D); + GL11.glDisable(GL_LIGHTING); + + /** + * GL11.glLogicOp(GL11.GL_INVERT); + * GL11.glEnable(GL11.GL_COLOR_LOGIC_OP); + */ + + // draws the verticies corresponding to the passed it + this.drawCrack(((TileEntityRift) te).riftRotation, LSystem.curves.get("terdragon 7"), 3, xWorld, yWorld, zWorld); + + // reenable all the stuff we disabled + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glEnable(GL11.GL_LIGHTING); + GL11.glEnable(GL_TEXTURE_2D); + + GL11.glPopMatrix(); + } + + /** + * method that draws the fractal and applies animations/effects + * + * f + * + * @param riftRotation + * @param poly + * @param size + * @param xWorld + * @param yWorld + * @param zWorld + */ + public void drawCrack(int riftRotation, PolygonStorage poly, double size, double xWorld, double yWorld, double zWorld) + { + // calculate the proper size for the rift render + double scale = size / (poly.maxX - poly.minX); + + // calculate the midpoint of the fractal bounding box + double offsetX = ((poly.maxX + poly.minX)) / 2; + double offsetY = ((poly.maxY + poly.minY)) / 2; + double offsetZ = 0; + + // changes how far the triangles move + float motionMagnitude = 2.0F; + + // changes how quickly the triangles move + float motionSpeed = 800.0F; + + // number of individual jitter waveforms to generate + // changes how "together" the overall motions are + int jCount = 10; + + // Calculate jitter like for monoliths + float time = (float) (((Minecraft.getSystemTime() + 0xF1234568 * this.hashCode()) % 2000000) / motionSpeed); + double[] jitters = new double[jCount]; + + // generate a series of waveforms + for (int i = 0; i < jCount; i += 2) + { + jitters[i] = Math.sin((1F + i / 10F) * time) * Math.cos(1F - (i / 10F) * time) / motionMagnitude; + jitters[i + 1] = Math.cos((1F + i / 10F) * time) * Math.sin(1F - (i / 10F) * time) / motionMagnitude; + } + + // determines which jitter waveform we select. Modulo so the same point + // gets the same jitter waveform over multiple frames + int jIndex = 0; + + // set the color for the render + GL11.glColor4f(.15F, .15F, .1F, 1F); + + //set the blending mode + GL11.glEnable(GL_BLEND); + glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE); + + // start rendering triangles for the moving shards + GL11.glBegin(GL11.GL_TRIANGLES); + for (Point p : poly.points) + { + jIndex++; + + // calculate the rotation for the fractal, apply offset, and apply + // jitter + double x = (((p.x + jitters[(jIndex + 1) % jCount]) - offsetX) * Math.cos(Math.toRadians(riftRotation)) - jitters[(jIndex + 2) % jCount] + * Math.sin(Math.toRadians(riftRotation))); + double y = p.y + (jitters[jIndex % jCount]); + double z = (((p.x + jitters[(jIndex + 2) % jCount]) - offsetX) * Math.sin(Math.toRadians(riftRotation)) + 0 * Math + .cos(Math.toRadians(riftRotation))); + + // apply scaling + x *= scale; + y *= scale; + z *= scale; + + // apply transform to center the offset origin into the middle of a + // block + x += .5; + y += .5; + z += .5; + + // draw the vertex and apply the world (screenspace) relative + // coordinates + GL11.glVertex3d(xWorld + x, yWorld + y, zWorld + z); + } + GL11.glEnd(); + + //GL11.glDisable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_DST_COLOR); + //glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); + + GL11.glBegin(GL11.GL_TRIANGLES); + + // draw the next set of triangles to form a background and change their + // color slightly over time + for (Point p : poly.points) + { + jIndex++; + + double x = (((p.x) - offsetX) * Math.cos(Math.toRadians(riftRotation)) - 0 * Math.sin(Math.toRadians(riftRotation))); + double y = p.y; + double z = (((p.x) - offsetX) * Math.sin(Math.toRadians(riftRotation)) + 0 * Math.cos(Math.toRadians(riftRotation))); + + x *= scale; + y *= scale; + z *= scale; + + x += .5; + y += .5; + z += .5; + + // the additional divisors here determine the color of the + // stationary shards + if (jIndex % 3 == 0) + { + GL11.glColor4d(jitters[(jIndex + 1) % jCount] / 11, jitters[(jIndex + 2) % jCount] / 8, jitters[(jIndex) % jCount] / 8, 1); + } + + GL11.glVertex3d(xWorld + x, yWorld + y, zWorld + z); + } + // stop drawing triangles + GL11.glEnd(); + } +} \ No newline at end of file diff --git a/src/main/java/org/poly2tri/Poly2Tri.java b/src/main/java/org/poly2tri/Poly2Tri.java new file mode 100644 index 0000000..ec9f422 --- /dev/null +++ b/src/main/java/org/poly2tri/Poly2Tri.java @@ -0,0 +1,124 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri; + +import org.poly2tri.geometry.polygon.Polygon; +import org.poly2tri.geometry.polygon.PolygonSet; +import org.poly2tri.triangulation.Triangulatable; +import org.poly2tri.triangulation.TriangulationAlgorithm; +import org.poly2tri.triangulation.TriangulationContext; +import org.poly2tri.triangulation.TriangulationMode; +import org.poly2tri.triangulation.TriangulationProcess; +import org.poly2tri.triangulation.delaunay.sweep.DTSweep; +import org.poly2tri.triangulation.delaunay.sweep.DTSweepContext; +import org.poly2tri.triangulation.sets.ConstrainedPointSet; +import org.poly2tri.triangulation.sets.PointSet; +import org.poly2tri.triangulation.util.PolygonGenerator; + +public class Poly2Tri +{ + + private static final TriangulationAlgorithm _defaultAlgorithm = TriangulationAlgorithm.DTSweep; + + public static void triangulate( PolygonSet ps ) + { + TriangulationContext tcx = createContext( _defaultAlgorithm ); + for( Polygon p : ps.getPolygons() ) + { + tcx.prepareTriangulation( p ); + triangulate( tcx ); + tcx.clear(); + } + } + + public static void triangulate( Polygon p ) + { + triangulate( _defaultAlgorithm, p ); + } + + public static void triangulate( ConstrainedPointSet cps ) + { + triangulate( _defaultAlgorithm, cps ); + } + + public static void triangulate( PointSet ps ) + { + triangulate( _defaultAlgorithm, ps ); + } + + public static TriangulationContext createContext( TriangulationAlgorithm algorithm ) + { + switch( algorithm ) + { + case DTSweep: + default: + return new DTSweepContext(); + } + } + + public static void triangulate( TriangulationAlgorithm algorithm, + Triangulatable t ) + { + TriangulationContext tcx; + +// long time = System.nanoTime(); + tcx = createContext( algorithm ); + tcx.prepareTriangulation( t ); + triangulate( tcx ); +// logger.info( "Triangulation of {} points [{}ms]", tcx.getPoints().size(), ( System.nanoTime() - time ) / 1e6 ); + } + + public static void triangulate( TriangulationContext tcx ) + { + switch( tcx.algorithm() ) + { + case DTSweep: + default: + DTSweep.triangulate( (DTSweepContext)tcx ); + } + } + + /** + * Will do a warmup run to let the JVM optimize the triangulation code + */ + public static void warmup() + { + /* + * After a method is run 10000 times, the Hotspot compiler will compile + * it into native code. Periodically, the Hotspot compiler may recompile + * the method. After an unspecified amount of time, then the compilation + * system should become quiet. + */ + Polygon poly = PolygonGenerator.RandomCircleSweep2( 50, 50000 ); + TriangulationProcess process = new TriangulationProcess(); + process.triangulate( poly ); + } +} diff --git a/src/main/java/org/poly2tri/geometry/polygon/Polygon.java b/src/main/java/org/poly2tri/geometry/polygon/Polygon.java new file mode 100644 index 0000000..e972033 --- /dev/null +++ b/src/main/java/org/poly2tri/geometry/polygon/Polygon.java @@ -0,0 +1,269 @@ +package org.poly2tri.geometry.polygon; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.poly2tri.triangulation.Triangulatable; +import org.poly2tri.triangulation.TriangulationContext; +import org.poly2tri.triangulation.TriangulationMode; +import org.poly2tri.triangulation.TriangulationPoint; +import org.poly2tri.triangulation.delaunay.DelaunayTriangle; + +public class Polygon implements Triangulatable +{ + + protected ArrayList _points = new ArrayList(); + protected ArrayList _steinerPoints; + protected ArrayList _holes; + + protected List m_triangles; + + protected PolygonPoint _last; + + /** + * To create a polygon we need atleast 3 separate points + * + * @param p1 + * @param p2 + * @param p3 + */ + public Polygon( PolygonPoint p1, PolygonPoint p2, PolygonPoint p3 ) + { + p1._next = p2; + p2._next = p3; + p3._next = p1; + p1._previous = p3; + p2._previous = p1; + p3._previous = p2; + _points.add( p1 ); + _points.add( p2 ); + _points.add( p3 ); + } + + /** + * Requires atleast 3 points + * @param points - ordered list of points forming the polygon. + * No duplicates are allowed + */ + public Polygon( List points ) + { + // Lets do one sanity check that first and last point hasn't got same position + // Its something that often happen when importing polygon data from other formats + if( points.get(0).equals( points.get(points.size()-1) ) ) + { + points.remove( points.size()-1 ); + } + _points.addAll( points ); + } + + /** + * Requires atleast 3 points + * + * @param points + */ + public Polygon( PolygonPoint[] points ) + { + this( Arrays.asList( points ) ); + } + + public TriangulationMode getTriangulationMode() + { + return TriangulationMode.POLYGON; + } + + public int pointCount() + { + int count = _points.size(); + if( _steinerPoints != null ) + { + count += _steinerPoints.size(); + } + return count; + } + + public void addSteinerPoint( TriangulationPoint point ) + { + if( _steinerPoints == null ) + { + _steinerPoints = new ArrayList(); + } + _steinerPoints.add( point ); + } + + public void addSteinerPoints( List points ) + { + if( _steinerPoints == null ) + { + _steinerPoints = new ArrayList(); + } + _steinerPoints.addAll( points ); + } + + public void clearSteinerPoints() + { + if( _steinerPoints != null ) + { + _steinerPoints.clear(); + } + } + + /** + * Assumes: that given polygon is fully inside the current polygon + * @param poly - a subtraction polygon + */ + public void addHole( Polygon poly ) + { + if( _holes == null ) + { + _holes = new ArrayList(); + } + _holes.add( poly ); + // XXX: tests could be made here to be sure it is fully inside +// addSubtraction( poly.getPoints() ); + } + + /** + * Will insert a point in the polygon after given point + * + * @param a + * @param b + * @param p + */ + public void insertPointAfter( PolygonPoint a, PolygonPoint newPoint ) + { + // Validate that + int index = _points.indexOf( a ); + if( index != -1 ) + { + newPoint.setNext( a.getNext() ); + newPoint.setPrevious( a ); + a.getNext().setPrevious( newPoint ); + a.setNext( newPoint ); + _points.add( index+1, newPoint ); + } + else + { + throw new RuntimeException( "Tried to insert a point into a Polygon after a point not belonging to the Polygon" ); + } + } + + public void addPoints( List list ) + { + PolygonPoint first; + for( PolygonPoint p : list ) + { + p.setPrevious( _last ); + if( _last != null ) + { + p.setNext( _last.getNext() ); + _last.setNext( p ); + } + _last = p; + _points.add( p ); + } + first = (PolygonPoint)_points.get(0); + _last.setNext( first ); + first.setPrevious( _last ); + } + + /** + * Will add a point after the last point added + * + * @param p + */ + public void addPoint(PolygonPoint p ) + { + p.setPrevious( _last ); + p.setNext( _last.getNext() ); + _last.setNext( p ); + _points.add( p ); + } + + public void removePoint( PolygonPoint p ) + { + PolygonPoint next, prev; + + next = p.getNext(); + prev = p.getPrevious(); + prev.setNext( next ); + next.setPrevious( prev ); + _points.remove( p ); + } + + public PolygonPoint getPoint() + { + return _last; + } + + public List getPoints() + { + return _points; + } + + public List getTriangles() + { + return m_triangles; + } + + public void addTriangle( DelaunayTriangle t ) + { + m_triangles.add( t ); + } + + public void addTriangles( List list ) + { + m_triangles.addAll( list ); + } + + public void clearTriangulation() + { + if( m_triangles != null ) + { + m_triangles.clear(); + } + } + + /** + * Creates constraints and populates the context with points + */ + public void prepareTriangulation( TriangulationContext tcx ) + { + if( m_triangles == null ) + { + m_triangles = new ArrayList( _points.size() ); + } + else + { + m_triangles.clear(); + } + + // Outer constraints + for( int i = 0; i < _points.size()-1 ; i++ ) + { + tcx.newConstraint( _points.get( i ), _points.get( i+1 ) ); + } + tcx.newConstraint( _points.get( 0 ), _points.get( _points.size()-1 ) ); + tcx.addPoints( _points ); + + // Hole constraints + if( _holes != null ) + { + for( Polygon p : _holes ) + { + for( int i = 0; i < p._points.size()-1 ; i++ ) + { + tcx.newConstraint( p._points.get( i ), p._points.get( i+1 ) ); + } + tcx.newConstraint( p._points.get( 0 ), p._points.get( p._points.size()-1 ) ); + tcx.addPoints( p._points ); + } + } + + if( _steinerPoints != null ) + { + tcx.addPoints( _steinerPoints ); + } + } + +} diff --git a/src/main/java/org/poly2tri/geometry/polygon/PolygonPoint.java b/src/main/java/org/poly2tri/geometry/polygon/PolygonPoint.java new file mode 100644 index 0000000..a0e3cf1 --- /dev/null +++ b/src/main/java/org/poly2tri/geometry/polygon/PolygonPoint.java @@ -0,0 +1,39 @@ +package org.poly2tri.geometry.polygon; + +import org.poly2tri.triangulation.point.TPoint; + +public class PolygonPoint extends TPoint +{ + protected PolygonPoint _next; + protected PolygonPoint _previous; + + public PolygonPoint( double x, double y ) + { + super( x, y ); + } + + public PolygonPoint( double x, double y, double z ) + { + super( x, y, z ); + } + + public void setPrevious( PolygonPoint p ) + { + _previous = p; + } + + public void setNext( PolygonPoint p ) + { + _next = p; + } + + public PolygonPoint getNext() + { + return _next; + } + + public PolygonPoint getPrevious() + { + return _previous; + } +} diff --git a/src/main/java/org/poly2tri/geometry/polygon/PolygonSet.java b/src/main/java/org/poly2tri/geometry/polygon/PolygonSet.java new file mode 100644 index 0000000..d7e33bb --- /dev/null +++ b/src/main/java/org/poly2tri/geometry/polygon/PolygonSet.java @@ -0,0 +1,58 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.geometry.polygon; + +import java.util.ArrayList; +import java.util.List; + +public class PolygonSet +{ + protected ArrayList _polygons = new ArrayList(); + + public PolygonSet() + { + } + + public PolygonSet( Polygon poly ) + { + _polygons.add( poly ); + } + + public void add( Polygon p ) + { + _polygons.add( p ); + } + + public List getPolygons() + { + return _polygons; + } +} diff --git a/src/main/java/org/poly2tri/geometry/polygon/PolygonUtil.java b/src/main/java/org/poly2tri/geometry/polygon/PolygonUtil.java new file mode 100644 index 0000000..3ca71de --- /dev/null +++ b/src/main/java/org/poly2tri/geometry/polygon/PolygonUtil.java @@ -0,0 +1,15 @@ +package org.poly2tri.geometry.polygon; + +public class PolygonUtil +{ + /** + * TODO + * @param polygon + */ + public static void validate( Polygon polygon ) + { + // TODO: implement + // 1. Check for duplicate points + // 2. Check for intersecting sides + } +} diff --git a/src/main/java/org/poly2tri/geometry/primitives/Edge.java b/src/main/java/org/poly2tri/geometry/primitives/Edge.java new file mode 100644 index 0000000..49b00b6 --- /dev/null +++ b/src/main/java/org/poly2tri/geometry/primitives/Edge.java @@ -0,0 +1,17 @@ +package org.poly2tri.geometry.primitives; + +public abstract class Edge +{ + protected A p; + protected A q; + + public A getP() + { + return p; + } + + public A getQ() + { + return q; + } +} diff --git a/src/main/java/org/poly2tri/geometry/primitives/Point.java b/src/main/java/org/poly2tri/geometry/primitives/Point.java new file mode 100644 index 0000000..680c141 --- /dev/null +++ b/src/main/java/org/poly2tri/geometry/primitives/Point.java @@ -0,0 +1,31 @@ +package org.poly2tri.geometry.primitives; + +public abstract class Point +{ + public abstract double getX(); + public abstract double getY(); + public abstract double getZ(); + + public abstract float getXf(); + public abstract float getYf(); + public abstract float getZf(); + + public abstract void set( double x, double y, double z ); + + protected static int calculateHashCode( double x, double y, double z) + { + int result = 17; + + final long a = Double.doubleToLongBits(x); + result += 31 * result + (int) (a ^ (a >>> 32)); + + final long b = Double.doubleToLongBits(y); + result += 31 * result + (int) (b ^ (b >>> 32)); + + final long c = Double.doubleToLongBits(z); + result += 31 * result + (int) (c ^ (c >>> 32)); + + return result; + + } +} diff --git a/src/main/java/org/poly2tri/transform/coordinate/AnyToXYTransform.java b/src/main/java/org/poly2tri/transform/coordinate/AnyToXYTransform.java new file mode 100644 index 0000000..97026a5 --- /dev/null +++ b/src/main/java/org/poly2tri/transform/coordinate/AnyToXYTransform.java @@ -0,0 +1,71 @@ +package org.poly2tri.transform.coordinate; + +/** + * A transform that aligns given source normal with the XY plane normal [0,0,1] + * + * @author thahlen@gmail.com + */ + +public class AnyToXYTransform extends Matrix3Transform +{ + /** + * Assumes source normal is normalized + */ + public AnyToXYTransform( double nx, double ny, double nz ) + { + setSourceNormal( nx, ny, nz ); + } + + /** + * Assumes source normal is normalized + * + * @param nx + * @param ny + * @param nz + */ + public void setSourceNormal( double nx, double ny, double nz ) + { + double h,f,c,vx,vy,hvx; + + vx = -ny; + vy = nx; + c = nz; + + h = (1-c)/(1-c*c); + hvx = h*vx; + f = (c < 0) ? -c : c; + + if( f < 1.0 - 1.0E-4 ) + { + m00=c + hvx*vx; + m01=hvx*vy; + m02=-vy; + m10=hvx*vy; + m11=c + h*vy*vy; + m12=vx; + m20=vy; + m21=-vx; + m22=c; + } + else + { + // if "from" and "to" vectors are nearly parallel + m00=1; + m01=0; + m02=0; + m10=0; + m11=1; + m12=0; + m20=0; + m21=0; + if( c > 0 ) + { + m22=1; + } + else + { + m22=-1; + } + } + } +} diff --git a/src/main/java/org/poly2tri/transform/coordinate/CoordinateTransform.java b/src/main/java/org/poly2tri/transform/coordinate/CoordinateTransform.java new file mode 100644 index 0000000..f00cb1c --- /dev/null +++ b/src/main/java/org/poly2tri/transform/coordinate/CoordinateTransform.java @@ -0,0 +1,12 @@ +package org.poly2tri.transform.coordinate; + +import java.util.List; + +import org.poly2tri.geometry.primitives.Point; + +public abstract interface CoordinateTransform +{ + public abstract void transform( Point p, Point store ); + public abstract void transform( Point p ); + public abstract void transform( List list ); +} diff --git a/src/main/java/org/poly2tri/transform/coordinate/Matrix3Transform.java b/src/main/java/org/poly2tri/transform/coordinate/Matrix3Transform.java new file mode 100644 index 0000000..f8422bc --- /dev/null +++ b/src/main/java/org/poly2tri/transform/coordinate/Matrix3Transform.java @@ -0,0 +1,38 @@ +package org.poly2tri.transform.coordinate; + +import java.util.List; + +import org.poly2tri.geometry.primitives.Point; + +public abstract class Matrix3Transform implements CoordinateTransform +{ + protected double m00,m01,m02,m10,m11,m12,m20,m21,m22; + + public void transform( Point p, Point store ) + { + final double px = p.getX(); + final double py = p.getY(); + final double pz = p.getZ(); + store.set(m00 * px + m01 * py + m02 * pz, + m10 * px + m11 * py + m12 * pz, + m20 * px + m21 * py + m22 * pz ); + } + + public void transform( Point p ) + { + final double px = p.getX(); + final double py = p.getY(); + final double pz = p.getZ(); + p.set(m00 * px + m01 * py + m02 * pz, + m10 * px + m11 * py + m12 * pz, + m20 * px + m21 * py + m22 * pz ); + } + + public void transform( List list ) + { + for( Point p : list ) + { + transform( p ); + } + } +} diff --git a/src/main/java/org/poly2tri/transform/coordinate/NoTransform.java b/src/main/java/org/poly2tri/transform/coordinate/NoTransform.java new file mode 100644 index 0000000..2f43cbc --- /dev/null +++ b/src/main/java/org/poly2tri/transform/coordinate/NoTransform.java @@ -0,0 +1,21 @@ +package org.poly2tri.transform.coordinate; + +import java.util.List; + +import org.poly2tri.geometry.primitives.Point; + +public class NoTransform implements CoordinateTransform +{ + public void transform( Point p, Point store ) + { + store.set( p.getX(), p.getY(), p.getZ() ); + } + + public void transform( Point p ) + { + } + + public void transform( List list ) + { + } +} diff --git a/src/main/java/org/poly2tri/transform/coordinate/XYToAnyTransform.java b/src/main/java/org/poly2tri/transform/coordinate/XYToAnyTransform.java new file mode 100644 index 0000000..6c0a30f --- /dev/null +++ b/src/main/java/org/poly2tri/transform/coordinate/XYToAnyTransform.java @@ -0,0 +1,74 @@ +package org.poly2tri.transform.coordinate; + +/** + * A transform that aligns the XY plane normal [0,0,1] with any given target normal + * + * http://www.cs.brown.edu/~jfh/papers/Moller-EBA-1999/paper.pdf + * + * @author thahlen@gmail.com + * + */ +public class XYToAnyTransform extends Matrix3Transform +{ + /** + * Assumes target normal is normalized + */ + public XYToAnyTransform( double nx, double ny, double nz ) + { + setTargetNormal( nx, ny, nz ); + } + + /** + * Assumes target normal is normalized + * + * @param nx + * @param ny + * @param nz + */ + public void setTargetNormal( double nx, double ny, double nz ) + { + double h,f,c,vx,vy,hvx; + + vx = ny; + vy = -nx; + c = nz; + + h = (1-c)/(1-c*c); + hvx = h*vx; + f = (c < 0) ? -c : c; + + if( f < 1.0 - 1.0E-4 ) + { + m00=c + hvx*vx; + m01=hvx*vy; + m02=-vy; + m10=hvx*vy; + m11=c + h*vy*vy; + m12=vx; + m20=vy; + m21=-vx; + m22=c; + } + else + { + // if "from" and "to" vectors are nearly parallel + m00=1; + m01=0; + m02=0; + m10=0; + m11=1; + m12=0; + m20=0; + m21=0; + if( c > 0 ) + { + m22=1; + } + else + { + m22=-1; + } + } + + } +} diff --git a/src/main/java/org/poly2tri/triangulation/Triangulatable.java b/src/main/java/org/poly2tri/triangulation/Triangulatable.java new file mode 100644 index 0000000..dddc620 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/Triangulatable.java @@ -0,0 +1,22 @@ +package org.poly2tri.triangulation; + +import java.util.List; + +import org.poly2tri.triangulation.delaunay.DelaunayTriangle; + +public interface Triangulatable +{ + /** + * Preparations needed before triangulation start should be handled here + * @param tcx + */ + public void prepareTriangulation( TriangulationContext tcx ); + + public List getTriangles(); + public List getPoints(); + public void addTriangle( DelaunayTriangle t ); + public void addTriangles( List list ); + public void clearTriangulation(); + + public TriangulationMode getTriangulationMode(); +} diff --git a/src/main/java/org/poly2tri/triangulation/TriangulationAlgorithm.java b/src/main/java/org/poly2tri/triangulation/TriangulationAlgorithm.java new file mode 100644 index 0000000..497aada --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/TriangulationAlgorithm.java @@ -0,0 +1,36 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation; + +public enum TriangulationAlgorithm +{ + DTSweep +} diff --git a/src/main/java/org/poly2tri/triangulation/TriangulationConstraint.java b/src/main/java/org/poly2tri/triangulation/TriangulationConstraint.java new file mode 100644 index 0000000..0e3f0d2 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/TriangulationConstraint.java @@ -0,0 +1,55 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation; + +/** + * Forces a triangle edge between two points p and q + * when triangulating. For example used to enforce + * Polygon Edges during a polygon triangulation. + * + * @author Thomas Åhlén, thahlen@gmail.com + */ +public class TriangulationConstraint +{ + protected TriangulationPoint p; + protected TriangulationPoint q; + + public TriangulationPoint getP() + { + return p; + } + + public TriangulationPoint getQ() + { + return q; + } + +} diff --git a/src/main/java/org/poly2tri/triangulation/TriangulationContext.java b/src/main/java/org/poly2tri/triangulation/TriangulationContext.java new file mode 100644 index 0000000..71b26bc --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/TriangulationContext.java @@ -0,0 +1,171 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation; + +import java.util.ArrayList; +import java.util.List; + +import org.poly2tri.triangulation.delaunay.DelaunayTriangle; + +public abstract class TriangulationContext +{ + protected A _debug; + protected boolean _debugEnabled = false; + + protected ArrayList _triList = new ArrayList(); + + protected ArrayList _points = new ArrayList(200); + protected TriangulationMode _triangulationMode; + protected Triangulatable _triUnit; + + private boolean _terminated = false; + private boolean _waitUntilNotified; + + private int _stepTime = -1; + private int _stepCount = 0; + public int getStepCount() { return _stepCount; } + + public void done() + { + _stepCount++; + } + + public abstract TriangulationAlgorithm algorithm(); + + public void prepareTriangulation( Triangulatable t ) + { + _triUnit = t; + _triangulationMode = t.getTriangulationMode(); + t.prepareTriangulation( this ); + } + + public abstract TriangulationConstraint newConstraint( TriangulationPoint a, TriangulationPoint b ); + + public void addToList( DelaunayTriangle triangle ) + { + _triList.add( triangle ); + } + + public List getTriangles() + { + return _triList; + } + + public Triangulatable getTriangulatable() + { + return _triUnit; + } + + public List getPoints() + { + return _points; + } + + public synchronized void update(String message) + { + if( _debugEnabled ) + { + try + { + synchronized( this ) + { + _stepCount++; + if( _stepTime > 0 ) + { + wait( (int)_stepTime ); + /** Can we resume execution or are we expected to wait? */ + if( _waitUntilNotified ) + { + wait(); + } + } + else + { + wait(); + } + // We have been notified + _waitUntilNotified = false; + } + } + catch( InterruptedException e ) + { + update("Triangulation was interrupted"); + } + } + if( _terminated ) + { + throw new RuntimeException( "Triangulation process terminated before completion"); + } + } + + public void clear() + { + _points.clear(); + _terminated = false; + if( _debug != null ) + { + _debug.clear(); + } + _stepCount=0; + } + + public TriangulationMode getTriangulationMode() + { + return _triangulationMode; + } + + public synchronized void waitUntilNotified(boolean b) + { + _waitUntilNotified = b; + } + + public void terminateTriangulation() + { + _terminated=true; + } + + public boolean isDebugEnabled() + { + return _debugEnabled; + } + + public abstract void isDebugEnabled( boolean b ); + + public A getDebugContext() + { + return _debug; + } + + public void addPoints( List points ) + { + _points.addAll( points ); + } +} diff --git a/src/main/java/org/poly2tri/triangulation/TriangulationDebugContext.java b/src/main/java/org/poly2tri/triangulation/TriangulationDebugContext.java new file mode 100644 index 0000000..a6eca87 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/TriangulationDebugContext.java @@ -0,0 +1,13 @@ +package org.poly2tri.triangulation; + +public abstract class TriangulationDebugContext +{ + protected TriangulationContext _tcx; + + public TriangulationDebugContext( TriangulationContext tcx ) + { + _tcx = tcx; + } + + public abstract void clear(); +} diff --git a/src/main/java/org/poly2tri/triangulation/TriangulationMode.java b/src/main/java/org/poly2tri/triangulation/TriangulationMode.java new file mode 100644 index 0000000..946862d --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/TriangulationMode.java @@ -0,0 +1,6 @@ +package org.poly2tri.triangulation; + +public enum TriangulationMode +{ + UNCONSTRAINED,CONSTRAINED,POLYGON; +} diff --git a/src/main/java/org/poly2tri/triangulation/TriangulationPoint.java b/src/main/java/org/poly2tri/triangulation/TriangulationPoint.java new file mode 100644 index 0000000..37686a5 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/TriangulationPoint.java @@ -0,0 +1,112 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation; + +import java.util.ArrayList; + +import org.poly2tri.geometry.primitives.Point; +import org.poly2tri.triangulation.delaunay.sweep.DTSweepConstraint; + + +public abstract class TriangulationPoint extends Point +{ + // List of edges this point constitutes an upper ending point (CDT) + private ArrayList edges; + + @Override + public String toString() + { + return "[" + getX() + "," + getY() + "]"; + } + + public abstract double getX(); + public abstract double getY(); + public abstract double getZ(); + + public abstract float getXf(); + public abstract float getYf(); + public abstract float getZf(); + + public abstract void set( double x, double y, double z ); + + public ArrayList getEdges() + { + return edges; + } + + public void addEdge( DTSweepConstraint e ) + { + if( edges == null ) + { + edges = new ArrayList(); + } + edges.add( e ); + } + + public boolean hasEdges() + { + return edges != null; + } + + /** + * @param p - edge destination point + * @return the edge from this point to given point + */ + public DTSweepConstraint getEdge( TriangulationPoint p ) + { + for( DTSweepConstraint c : edges ) + { + if( c.p == p ) + { + return c; + } + } + return null; + } + + public boolean equals(Object obj) + { + if( obj instanceof TriangulationPoint ) + { + TriangulationPoint p = (TriangulationPoint)obj; + return getX() == p.getX() && getY() == p.getY(); + } + return super.equals( obj ); + } + + public int hashCode() + { + long bits = java.lang.Double.doubleToLongBits(getX()); + bits ^= java.lang.Double.doubleToLongBits(getY()) * 31; + return (((int) bits) ^ ((int) (bits >> 32))); + } + +} diff --git a/src/main/java/org/poly2tri/triangulation/TriangulationProcess.java b/src/main/java/org/poly2tri/triangulation/TriangulationProcess.java new file mode 100644 index 0000000..35baf1f --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/TriangulationProcess.java @@ -0,0 +1,341 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation; + +import java.lang.Thread.State; +import java.util.ArrayList; +import java.util.List; + +import org.poly2tri.Poly2Tri; +import org.poly2tri.geometry.polygon.Polygon; +import org.poly2tri.geometry.polygon.PolygonSet; +import org.poly2tri.triangulation.sets.ConstrainedPointSet; +import org.poly2tri.triangulation.sets.PointSet; + + +/** + * + * @author Thomas Åhlén, thahlen@gmail.com + * + */ +public class TriangulationProcess implements Runnable +{ + + private final TriangulationAlgorithm _algorithm; + + private TriangulationContext _tcx; + private Thread _thread; + private boolean _isTerminated = false; + private int _pointCount = 0; + private long _timestamp = 0; + private double _triangulationTime = 0; + + private boolean _awaitingTermination; + private boolean _restart = false; + + private ArrayList _triangulations = new ArrayList(); + + private ArrayList _listeners = new ArrayList(); + + public void addListener( TriangulationProcessListener listener ) + { + _listeners.add( listener ); + } + + public void removeListener( TriangulationProcessListener listener ) + { + _listeners.remove( listener ); + } + + public void clearListeners() + { + _listeners.clear(); + } + + /** + * Notify all listeners of this new event + * @param event + */ + private void sendEvent( TriangulationProcessEvent event ) + { + for( TriangulationProcessListener l : _listeners ) + { + l.triangulationEvent( event, _tcx.getTriangulatable() ); + } + } + + public int getStepCount() + { + return _tcx.getStepCount(); + } + + public long getTimestamp() + { + return _timestamp; + } + + public double getTriangulationTime() + { + return _triangulationTime; + } + + /** + * Uses SweepLine algorithm by default + * @param algorithm + */ + public TriangulationProcess() + { + this( TriangulationAlgorithm.DTSweep ); + } + + public TriangulationProcess( TriangulationAlgorithm algorithm ) + { + _algorithm = algorithm; + _tcx = Poly2Tri.createContext( algorithm ); + } + + /** + * This retriangulates same set as previous triangulation + * useful if you want to do consecutive triangulations with + * same data. Like when you when you want to do performance + * tests. + */ +// public void triangulate() +// { +// start(); +// } + + /** + * Triangulate a PointSet with eventual constraints + * + * @param cps + */ + public void triangulate( PointSet ps ) + { + _triangulations.clear(); + _triangulations.add( ps ); + start(); + } + + /** + * Triangulate a PointSet with eventual constraints + * + * @param cps + */ + public void triangulate( ConstrainedPointSet cps ) + { + _triangulations.clear(); + _triangulations.add( cps ); + start(); + } + + /** + * Triangulate a PolygonSet + * + * @param ps + */ + public void triangulate( PolygonSet ps ) + { + _triangulations.clear(); + _triangulations.addAll( ps.getPolygons() ); + start(); + } + + /** + * Triangulate a Polygon + * + * @param ps + */ + public void triangulate( Polygon polygon ) + { + _triangulations.clear(); + _triangulations.add( polygon ); + start(); + } + + /** + * Triangulate a List of Triangulatables + * + * @param ps + */ + public void triangulate( List list ) + { + _triangulations.clear(); + _triangulations.addAll( list ); + start(); + } + + private void start() + { + if( _thread == null || _thread.getState() == State.TERMINATED ) + { + _isTerminated = false; + _thread = new Thread( this, _algorithm.name() + "." + _tcx.getTriangulationMode() ); + _thread.start(); + sendEvent( TriangulationProcessEvent.Started ); + } + else + { + // Triangulation already running. Terminate it so we can start a new + shutdown(); + _restart = true; + } + } + + public boolean isWaiting() + { + if( _thread != null && _thread.getState() == State.WAITING ) + { + return true; + } + return false; + } + + public void run() + { + _pointCount=0; + try + { + long time = System.nanoTime(); + for( Triangulatable t : _triangulations ) + { + _tcx.clear(); + _tcx.prepareTriangulation( t ); + _pointCount += _tcx._points.size(); + Poly2Tri.triangulate( _tcx ); + } + _triangulationTime = ( System.nanoTime() - time ) / 1e6; + sendEvent( TriangulationProcessEvent.Done ); + } + catch( RuntimeException e ) + { + if( _awaitingTermination ) + { + _awaitingTermination = false; + sendEvent( TriangulationProcessEvent.Aborted ); + } + else + { + e.printStackTrace(); + sendEvent( TriangulationProcessEvent.Failed ); + } + } + catch( Exception e ) + { + e.printStackTrace(); + sendEvent( TriangulationProcessEvent.Failed ); + } + finally + { + _timestamp = System.currentTimeMillis(); + _isTerminated = true; + _thread = null; + } + + // Autostart a new triangulation? + if( _restart ) + { + _restart = false; + start(); + } + } + + public void resume() + { + if( _thread != null ) + { + // Only force a resume when process is waiting for a notification + if( _thread.getState() == State.WAITING ) + { + synchronized( _tcx ) + { + _tcx.notify(); + } + } + else if( _thread.getState() == State.TIMED_WAITING ) + { + _tcx.waitUntilNotified( false ); + } + } + } + + public void shutdown() + { + _awaitingTermination = true; + _tcx.terminateTriangulation(); + resume(); + } + + public TriangulationContext getContext() + { + return _tcx; + } + + public boolean isDone() + { + return _isTerminated; + } + + public void requestRead() + { + _tcx.waitUntilNotified( true ); + } + + public boolean isReadable() + { + if( _thread == null ) + { + return true; + } + else + { + synchronized( _thread ) + { + if( _thread.getState() == State.WAITING ) + { + return true; + } + else if( _thread.getState() == State.TIMED_WAITING ) + { + // Make sure that it stays readable + _tcx.waitUntilNotified( true ); + return true; + } + return false; + } + } + } + + public int getPointCount() + { + return _pointCount; + } +} diff --git a/src/main/java/org/poly2tri/triangulation/TriangulationProcessEvent.java b/src/main/java/org/poly2tri/triangulation/TriangulationProcessEvent.java new file mode 100644 index 0000000..dce9e04 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/TriangulationProcessEvent.java @@ -0,0 +1,36 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation; + +public enum TriangulationProcessEvent +{ + Started,Waiting,Failed,Aborted,Done +} diff --git a/src/main/java/org/poly2tri/triangulation/TriangulationProcessListener.java b/src/main/java/org/poly2tri/triangulation/TriangulationProcessListener.java new file mode 100644 index 0000000..922e3c9 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/TriangulationProcessListener.java @@ -0,0 +1,36 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation; + +public interface TriangulationProcessListener +{ + public void triangulationEvent( TriangulationProcessEvent e, Triangulatable unit ); +} diff --git a/src/main/java/org/poly2tri/triangulation/TriangulationUtil.java b/src/main/java/org/poly2tri/triangulation/TriangulationUtil.java new file mode 100644 index 0000000..9da0328 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/TriangulationUtil.java @@ -0,0 +1,213 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */package org.poly2tri.triangulation; + + +/** + * @author Thomas Åhlén, thahlen@gmail.com + */ +public class TriangulationUtil +{ + public final static double EPSILON = 1e-12; + + // Returns triangle circumcircle point and radius +// public static Tuple2 circumCircle( TPoint a, TPoint b, TPoint c ) +// { +// double A = det( a, b, c ); +// double C = detC( a, b, c ); +// +// double sa = a.getX() * a.getX() + a.getY() * a.getY(); +// double sb = b.getX() * b.getX() + b.getY() * b.getY(); +// double sc = c.getX() * c.getX() + c.getY() * c.getY(); +// +// TPoint bx1 = new TPoint( sa, a.getY() ); +// TPoint bx2 = new TPoint( sb, b.getY() ); +// TPoint bx3 = new TPoint( sc, c.getY() ); +// double bx = det( bx1, bx2, bx3 ); +// +// TPoint by1 = new TPoint( sa, a.getX() ); +// TPoint by2 = new TPoint( sb, b.getX() ); +// TPoint by3 = new TPoint( sc, c.getX() ); +// double by = det( by1, by2, by3 ); +// +// double x = bx / ( 2 * A ); +// double y = by / ( 2 * A ); +// +// TPoint center = new TPoint( x, y ); +// double radius = Math.sqrt( bx * bx + by * by - 4 * A * C ) / ( 2 * Math.abs( A ) ); +// +// return new Tuple2( center, radius ); +// } + + /** + * Requirement:
+ * 1. a,b and c form a triangle.
+ * 2. a and d is know to be on opposite side of bc
+ *
+     *                a
+     *                +
+     *               / \
+     *              /   \
+     *            b/     \c
+     *            +-------+ 
+     *           /    B    \  
+     *          /           \ 
+     * 
+ * Fact: d has to be in area B to have a chance to be inside the circle formed by + * a,b and c
+ * d is outside B if orient2d(a,b,d) or orient2d(c,a,d) is CW
+ * This preknowledge gives us a way to optimize the incircle test + * @param a - triangle point, opposite d + * @param b - triangle point + * @param c - triangle point + * @param d - point opposite a + * @return true if d is inside circle, false if on circle edge + */ + public static boolean smartIncircle( final TriangulationPoint pa, + final TriangulationPoint pb, + final TriangulationPoint pc, + final TriangulationPoint pd ) + { + final double pdx = pd.getX(); + final double pdy = pd.getY(); + final double adx = pa.getX() - pdx; + final double ady = pa.getY() - pdy; + final double bdx = pb.getX() - pdx; + final double bdy = pb.getY() - pdy; + + final double adxbdy = adx * bdy; + final double bdxady = bdx * ady; + final double oabd = adxbdy - bdxady; +// oabd = orient2d(pa,pb,pd); + if( oabd <= 0 ) + { + return false; + } + + final double cdx = pc.getX() - pdx; + final double cdy = pc.getY() - pdy; + + final double cdxady = cdx * ady; + final double adxcdy = adx * cdy; + final double ocad = cdxady - adxcdy; +// ocad = orient2d(pc,pa,pd); + if( ocad <= 0 ) + { + return false; + } + + final double bdxcdy = bdx * cdy; + final double cdxbdy = cdx * bdy; + + final double alift = adx * adx + ady * ady; + final double blift = bdx * bdx + bdy * bdy; + final double clift = cdx * cdx + cdy * cdy; + + final double det = alift * ( bdxcdy - cdxbdy ) + blift * ocad + clift * oabd; + + return det > 0; + } + + /** + * @see smartIncircle + * @param pa + * @param pb + * @param pc + * @param pd + * @return + */ + public static boolean inScanArea( final TriangulationPoint pa, + final TriangulationPoint pb, + final TriangulationPoint pc, + final TriangulationPoint pd ) + { + final double pdx = pd.getX(); + final double pdy = pd.getY(); + final double adx = pa.getX() - pdx; + final double ady = pa.getY() - pdy; + final double bdx = pb.getX() - pdx; + final double bdy = pb.getY() - pdy; + + final double adxbdy = adx * bdy; + final double bdxady = bdx * ady; + final double oabd = adxbdy - bdxady; +// oabd = orient2d(pa,pb,pd); + if( oabd <= 0 ) + { + return false; + } + + final double cdx = pc.getX() - pdx; + final double cdy = pc.getY() - pdy; + + final double cdxady = cdx * ady; + final double adxcdy = adx * cdy; + final double ocad = cdxady - adxcdy; +// ocad = orient2d(pc,pa,pd); + if( ocad <= 0 ) + { + return false; + } + return true; + } + + /** + * Forumla to calculate signed area
+ * Positive if CCW
+ * Negative if CW
+ * 0 if collinear
+ *
+     * A[P1,P2,P3]  =  (x1*y2 - y1*x2) + (x2*y3 - y2*x3) + (x3*y1 - y3*x1)
+     *              =  (x1-x3)*(y2-y3) - (y1-y3)*(x2-x3)
+     * 
+ */ + public static Orientation orient2d( TriangulationPoint pa, + TriangulationPoint pb, + TriangulationPoint pc ) + { + double detleft = ( pa.getX() - pc.getX() ) * ( pb.getY() - pc.getY() ); + double detright = ( pa.getY() - pc.getY() ) * ( pb.getX() - pc.getX() ); + double val = detleft - detright; + if( val > -EPSILON && val < EPSILON ) + { + return Orientation.Collinear; + } + else if( val > 0 ) + { + return Orientation.CCW; + } + return Orientation.CW; + } + + public enum Orientation + { + CW,CCW,Collinear; + } +} diff --git a/src/main/java/org/poly2tri/triangulation/delaunay/DelaunayTriangle.java b/src/main/java/org/poly2tri/triangulation/delaunay/DelaunayTriangle.java new file mode 100644 index 0000000..c93fcb0 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/delaunay/DelaunayTriangle.java @@ -0,0 +1,685 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation.delaunay; + +import java.util.ArrayList; + +import org.poly2tri.triangulation.TriangulationPoint; +import org.poly2tri.triangulation.delaunay.sweep.DTSweepConstraint; +import org.poly2tri.triangulation.point.TPoint; + + +public class DelaunayTriangle +{ + + /** Neighbor pointers */ + public final DelaunayTriangle[] neighbors = new DelaunayTriangle[3]; + /** Flags to determine if an edge is a Constrained edge */ + public final boolean[] cEdge = new boolean[] { false, false, false }; + /** Flags to determine if an edge is a Delauney edge */ + public final boolean[] dEdge = new boolean[] { false, false, false }; + /** Has this triangle been marked as an interior triangle? */ + protected boolean interior = false; + + public final TriangulationPoint[] points = new TriangulationPoint[3]; + + public DelaunayTriangle( TriangulationPoint p1, TriangulationPoint p2, TriangulationPoint p3 ) + { + points[0] = p1; + points[1] = p2; + points[2] = p3; + } + + public int index( TriangulationPoint p ) + { + if( p == points[0] ) + { + return 0; + } + else if( p == points[1] ) + { + return 1; + } + else if( p == points[2] ) + { + return 2; + } + throw new RuntimeException("Calling index with a point that doesn't exist in triangle"); + } + + public int indexCW( TriangulationPoint p ) + { + int index = index(p); + switch( index ) + { + case 0: return 2; + case 1: return 0; + default: return 1; + } + } + + public int indexCCW( TriangulationPoint p ) + { + int index = index(p); + switch( index ) + { + case 0: return 1; + case 1: return 2; + default: return 0; + } + } + + public boolean contains( TriangulationPoint p ) + { + return ( p == points[0] || p == points[1] || p == points[2] ); + } + + public boolean contains( DTSweepConstraint e ) + { + return ( contains( e.p ) && contains( e.q ) ); + } + + public boolean contains( TriangulationPoint p, TriangulationPoint q ) + { + return ( contains( p ) && contains( q ) ); + } + + // Update neighbor pointers + private void markNeighbor( TriangulationPoint p1, + TriangulationPoint p2, + DelaunayTriangle t ) + { + if( ( p1 == points[2] && p2 == points[1] ) || ( p1 == points[1] && p2 == points[2] ) ) + { + neighbors[0] = t; + } + else if( ( p1 == points[0] && p2 == points[2] ) || ( p1 == points[2] && p2 == points[0] ) ) + { + neighbors[1] = t; + } + else if( ( p1 == points[0] && p2 == points[1] ) || ( p1 == points[1] && p2 == points[0] ) ) + { + neighbors[2] = t; + } + else + { + // throw new Exception("Neighbor error, please report!"); + } + } + + /* Exhaustive search to update neighbor pointers */ + public void markNeighbor( DelaunayTriangle t ) + { + if( t.contains( points[1], points[2] ) ) + { + neighbors[0] = t; + t.markNeighbor( points[1], points[2], this ); + } + else if( t.contains( points[0], points[2] ) ) + { + neighbors[1] = t; + t.markNeighbor( points[0], points[2], this ); + } + else if( t.contains( points[0], points[1] ) ) + { + neighbors[2] = t; + t.markNeighbor( points[0], points[1], this ); + } + else + { + } + } + + public void clearNeighbors() + { + neighbors[0] = neighbors[1] = neighbors[2] = null; + } + + public void clearNeighbor( DelaunayTriangle triangle ) + { + if( neighbors[0] == triangle ) + { + neighbors[0] = null; + } + else if( neighbors[1] == triangle ) + { + neighbors[1] = null; + } + else + { + neighbors[2] = null; + } + } + + /** + * Clears all references to all other triangles and points + */ + public void clear() + { + DelaunayTriangle t; + for( int i=0; i<3; i++ ) + { + t = neighbors[i]; + if( t != null ) + { + t.clearNeighbor( this ); + } + } + clearNeighbors(); + points[0]=points[1]=points[2]=null; + } + /** + * @param t - opposite triangle + * @param p - the point in t that isn't shared between the triangles + * @return + */ + public TriangulationPoint oppositePoint( DelaunayTriangle t, TriangulationPoint p ) + { + assert t != this : "self-pointer error"; + return pointCW( t.pointCW(p) ); + } + + // The neighbor clockwise to given point + public DelaunayTriangle neighborCW( TriangulationPoint point ) + { + if( point == points[0] ) + { + return neighbors[1]; + } + else if( point == points[1] ) + { + return neighbors[2]; + } + return neighbors[0]; + } + + // The neighbor counter-clockwise to given point + public DelaunayTriangle neighborCCW( TriangulationPoint point ) + { + if( point == points[0] ) + { + return neighbors[2]; + } + else if( point == points[1] ) + { + return neighbors[0]; + } + return neighbors[1]; + } + + // The neighbor across to given point + public DelaunayTriangle neighborAcross( TriangulationPoint opoint ) + { + if( opoint == points[0] ) + { + return neighbors[0]; + } + else if( opoint == points[1] ) + { + return neighbors[1]; + } + return neighbors[2]; + } + + // The point counter-clockwise to given point + public TriangulationPoint pointCCW( TriangulationPoint point ) + { + if( point == points[0] ) + { + return points[1]; + } + else if( point == points[1] ) + { + return points[2]; + } + else if( point == points[2] ) + { + return points[0]; + } + throw new RuntimeException("[FIXME] point location error"); + } + + // The point counter-clockwise to given point + public TriangulationPoint pointCW( TriangulationPoint point ) + { + if( point == points[0] ) + { + return points[2]; + } + else if( point == points[1] ) + { + return points[0]; + } + else if( point == points[2] ) + { + return points[1]; + } + throw new RuntimeException("[FIXME] point location error"); + } + + // Legalize triangle by rotating clockwise around oPoint + public void legalize( TriangulationPoint oPoint, TriangulationPoint nPoint ) + { + if( oPoint == points[0] ) + { + points[1] = points[0]; + points[0] = points[2]; + points[2] = nPoint; + } + else if( oPoint == points[1] ) + { + points[2] = points[1]; + points[1] = points[0]; + points[0] = nPoint; + } + else if( oPoint == points[2] ) + { + points[0] = points[2]; + points[2] = points[1]; + points[1] = nPoint; + } + else + { + throw new RuntimeException("legalization bug"); + } + } + + public void printDebug() + { + System.out.println( points[0] + "," + points[1] + "," + points[2] ); + } + + // Finalize edge marking + public void markNeighborEdges() + { + for( int i = 0; i < 3; i++ ) + { + if( cEdge[i] ) + { + switch( i ) + { + case 0: + if( neighbors[0] != null ) + neighbors[0].markConstrainedEdge( points[1], points[2] ); + break; + case 1: + if( neighbors[1] != null ) + neighbors[1].markConstrainedEdge( points[0], points[2] ); + break; + case 2: + if( neighbors[2] != null ) + neighbors[2].markConstrainedEdge( points[0], points[1] ); + break; + } + } + } + } + + public void markEdge( DelaunayTriangle triangle ) + { + for( int i = 0; i < 3; i++ ) + { + if( cEdge[i] ) + { + switch( i ) + { + case 0: + triangle.markConstrainedEdge( points[1], points[2] ); + break; + case 1: + triangle.markConstrainedEdge( points[0], points[2] ); + break; + case 2: + triangle.markConstrainedEdge( points[0], points[1] ); + break; + } + } + } + } + + public void markEdge( ArrayList tList ) + { + + for( DelaunayTriangle t : tList ) + { + for( int i = 0; i < 3; i++ ) + { + if( t.cEdge[i] ) + { + switch( i ) + { + case 0: + markConstrainedEdge( t.points[1], t.points[2] ); + break; + case 1: + markConstrainedEdge( t.points[0], t.points[2] ); + break; + case 2: + markConstrainedEdge( t.points[0], t.points[1] ); + break; + } + } + } + } + } + + public void markConstrainedEdge( int index ) + { + cEdge[index] = true; + } + + public void markConstrainedEdge( DTSweepConstraint edge ) + { + markConstrainedEdge( edge.p, edge.q ); + if( ( edge.q == points[0] && edge.p == points[1] ) + || ( edge.q == points[1] && edge.p == points[0] ) ) + { + cEdge[2] = true; + } + else if( ( edge.q == points[0] && edge.p == points[2] ) + || ( edge.q == points[2] && edge.p == points[0] ) ) + { + cEdge[1] = true; + } + else if( ( edge.q == points[1] && edge.p == points[2] ) + || ( edge.q == points[2] && edge.p == points[1] ) ) + { + cEdge[0] = true; + } + } + + // Mark edge as constrained + public void markConstrainedEdge( TriangulationPoint p, TriangulationPoint q ) + { + if( ( q == points[0] && p == points[1] ) || ( q == points[1] && p == points[0] ) ) + { + cEdge[2] = true; + } + else if( ( q == points[0] && p == points[2] ) || ( q == points[2] && p == points[0] ) ) + { + cEdge[1] = true; + } + else if( ( q == points[1] && p == points[2] ) || ( q == points[2] && p == points[1] ) ) + { + cEdge[0] = true; + } + } + + public double area() + { + double a = (points[0].getX() - points[2].getX())*(points[1].getY() - points[0].getY()); + double b = (points[0].getX() - points[1].getX())*(points[2].getY() - points[0].getY()); + + return 0.5*Math.abs( a - b ); + } + + public TPoint centroid() + { + double cx = ( points[0].getX() + points[1].getX() + points[2].getX() ) / 3d; + double cy = ( points[0].getY() + points[1].getY() + points[2].getY() ) / 3d; + return new TPoint( cx, cy ); + } + + /** + * Get the neighbor that share this edge + * + * @param constrainedEdge + * @return index of the shared edge or -1 if edge isn't shared + */ + public int edgeIndex( TriangulationPoint p1, TriangulationPoint p2 ) + { + if( points[0] == p1 ) + { + if( points[1] == p2 ) + { + return 2; + } + else if( points[2] == p2 ) + { + return 1; + } + } + else if( points[1] == p1 ) + { + if( points[2] == p2 ) + { + return 0; + } + else if( points[0] == p2 ) + { + return 2; + } + } + else if( points[2] == p1 ) + { + if( points[0] == p2 ) + { + return 1; + } + else if( points[1] == p2 ) + { + return 0; + } + } + return -1; + } + + public boolean getConstrainedEdgeCCW( TriangulationPoint p ) + { + if( p == points[0] ) + { + return cEdge[2]; + } + else if( p == points[1] ) + { + return cEdge[0]; + } + return cEdge[1]; + } + + public boolean getConstrainedEdgeCW( TriangulationPoint p ) + { + if( p == points[0] ) + { + return cEdge[1]; + } + else if( p == points[1] ) + { + return cEdge[2]; + } + return cEdge[0]; + } + + public boolean getConstrainedEdgeAcross( TriangulationPoint p ) + { + if( p == points[0] ) + { + return cEdge[0]; + } + else if( p == points[1] ) + { + return cEdge[1]; + } + return cEdge[2]; + } + + public void setConstrainedEdgeCCW( TriangulationPoint p, boolean ce ) + { + if( p == points[0] ) + { + cEdge[2] = ce; + } + else if( p == points[1] ) + { + cEdge[0] = ce; + } + else + { + cEdge[1] = ce; + } + } + + public void setConstrainedEdgeCW( TriangulationPoint p, boolean ce ) + { + if( p == points[0] ) + { + cEdge[1] = ce; + } + else if( p == points[1] ) + { + cEdge[2] = ce; + } + else + { + cEdge[0] = ce; + } + } + + public void setConstrainedEdgeAcross( TriangulationPoint p, boolean ce ) + { + if( p == points[0] ) + { + cEdge[0] = ce; + } + else if( p == points[1] ) + { + cEdge[1] = ce; + } + else + { + cEdge[2] = ce; + } + } + + public boolean getDelunayEdgeCCW( TriangulationPoint p ) + { + if( p == points[0] ) + { + return dEdge[2]; + } + else if( p == points[1] ) + { + return dEdge[0]; + } + return dEdge[1]; + } + + public boolean getDelunayEdgeCW( TriangulationPoint p ) + { + if( p == points[0] ) + { + return dEdge[1]; + } + else if( p == points[1] ) + { + return dEdge[2]; + } + return dEdge[0]; + } + + public boolean getDelunayEdgeAcross( TriangulationPoint p ) + { + if( p == points[0] ) + { + return dEdge[0]; + } + else if( p == points[1] ) + { + return dEdge[1]; + } + return dEdge[2]; + } + + public void setDelunayEdgeCCW( TriangulationPoint p, boolean e ) + { + if( p == points[0] ) + { + dEdge[2] = e; + } + else if( p == points[1] ) + { + dEdge[0] = e; + } + else + { + dEdge[1] = e; + } + } + + public void setDelunayEdgeCW( TriangulationPoint p, boolean e ) + { + if( p == points[0] ) + { + dEdge[1] = e; + } + else if( p == points[1] ) + { + dEdge[2] = e; + } + else + { + dEdge[0] = e; + } + } + + public void setDelunayEdgeAcross( TriangulationPoint p, boolean e ) + { + if( p == points[0] ) + { + dEdge[0] = e; + } + else if( p == points[1] ) + { + dEdge[1] = e; + } + else + { + dEdge[2] = e; + } + } + + public void clearDelunayEdges() + { + dEdge[0] = false; + dEdge[1] = false; + dEdge[2] = false; + } + + public boolean isInterior() + { + return interior; + } + + public void isInterior( boolean b ) + { + interior = b; + } +} diff --git a/src/main/java/org/poly2tri/triangulation/delaunay/sweep/AdvancingFront.java b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/AdvancingFront.java new file mode 100644 index 0000000..e7994f3 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/AdvancingFront.java @@ -0,0 +1,179 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation.delaunay.sweep; + +import org.poly2tri.triangulation.TriangulationPoint; + + +/** + * @author Thomas Åhlen (thahlen@gmail.com) + */ +public class AdvancingFront +{ + public AdvancingFrontNode head; + public AdvancingFrontNode tail; + protected AdvancingFrontNode search; + + public AdvancingFront( AdvancingFrontNode head, AdvancingFrontNode tail ) + { + this.head = head; + this.tail = tail; + this.search = head; + addNode( head ); + addNode( tail ); + } + + public void addNode( AdvancingFrontNode node ) + { +// _searchTree.put( node.key, node ); + } + + public void removeNode( AdvancingFrontNode node ) + { +// _searchTree.delete( node.key ); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + AdvancingFrontNode node = head; + while( node != tail ) + { + sb.append( node.point.getX() ).append( "->" ); + node = node.next; + } + sb.append( tail.point.getX() ); + return sb.toString(); + } + + private final AdvancingFrontNode findSearchNode( double x ) + { + // TODO: implement BST index + return search; + } + + /** + * We use a balancing tree to locate a node smaller or equal to + * given key value + * + * @param x + * @return + */ + public AdvancingFrontNode locateNode( TriangulationPoint point ) + { + return locateNode( point.getX() ); + } + + private AdvancingFrontNode locateNode( double x ) + { + AdvancingFrontNode node = findSearchNode(x); + if( x < node.value ) + { + while( (node = node.prev) != null ) + { + if( x >= node.value ) + { + search = node; + return node; + } + } + } + else + { + while( (node = node.next) != null ) + { + if( x < node.value ) + { + search = node.prev; + return node.prev; + } + } + } + return null; + } + + /** + * This implementation will use simple node traversal algorithm to find + * a point on the front + * + * @param point + * @return + */ + public AdvancingFrontNode locatePoint( final TriangulationPoint point ) + { + final double px = point.getX(); + AdvancingFrontNode node = findSearchNode(px); + final double nx = node.point.getX(); + + if( px == nx ) + { + if( point != node.point ) + { + // We might have two nodes with same x value for a short time + if( point == node.prev.point ) + { + node = node.prev; + } + else if( point == node.next.point ) + { + node = node.next; + } + else + { + throw new RuntimeException( "Failed to find Node for given afront point"); +// node = null; + } + } + } + else if( px < nx ) + { + while( (node = node.prev) != null ) + { + if( point == node.point ) + { + break; + } + } + } + else + { + while( (node = node.next) != null ) + { + if( point == node.point ) + { + break; + } + } + } + search = node; + return node; + } +} \ No newline at end of file diff --git a/src/main/java/org/poly2tri/triangulation/delaunay/sweep/AdvancingFrontIndex.java b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/AdvancingFrontIndex.java new file mode 100644 index 0000000..8305416 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/AdvancingFrontIndex.java @@ -0,0 +1,43 @@ +package org.poly2tri.triangulation.delaunay.sweep; + +public class AdvancingFrontIndex
+{ + double _min,_max; + IndexNode _root; + + public AdvancingFrontIndex( double min, double max, int depth ) + { + if( depth > 5 ) depth = 5; + _root = createIndex( depth ); + } + + private IndexNode createIndex( int n ) + { + IndexNode node = null; + if( n > 0 ) + { + node = new IndexNode(); + node.bigger = createIndex( n-1 ); + node.smaller = createIndex( n-1 ); + } + return node; + } + + public A fetchAndRemoveIndex( A key ) + { + return null; + } + + public A fetchAndInsertIndex( A key ) + { + return null; + } + + class IndexNode + { + A value; + IndexNode smaller; + IndexNode bigger; + double range; + } +} diff --git a/src/main/java/org/poly2tri/triangulation/delaunay/sweep/AdvancingFrontNode.java b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/AdvancingFrontNode.java new file mode 100644 index 0000000..d986925 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/AdvancingFrontNode.java @@ -0,0 +1,84 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation.delaunay.sweep; + +import org.poly2tri.triangulation.TriangulationPoint; +import org.poly2tri.triangulation.delaunay.DelaunayTriangle; + + + +public class AdvancingFrontNode +{ + protected AdvancingFrontNode next = null; + protected AdvancingFrontNode prev = null; + + protected final Double key; // XXX: BST + protected final double value; + protected final TriangulationPoint point; + protected DelaunayTriangle triangle; + + public AdvancingFrontNode( TriangulationPoint point ) + { + this.point = point; + value = point.getX(); + key = Double.valueOf( value ); // XXX: BST + } + + public AdvancingFrontNode getNext() + { + return next; + } + + public AdvancingFrontNode getPrevious() + { + return prev; + } + + public TriangulationPoint getPoint() + { + return point; + } + + public DelaunayTriangle getTriangle() + { + return triangle; + } + + public boolean hasNext() + { + return next != null; + } + + public boolean hasPrevious() + { + return prev != null; + } +} diff --git a/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweep.java b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweep.java new file mode 100644 index 0000000..bd9fd35 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweep.java @@ -0,0 +1,1290 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation.delaunay.sweep; + +import static org.poly2tri.triangulation.TriangulationUtil.EPSILON; +import static org.poly2tri.triangulation.TriangulationUtil.inScanArea; +import static org.poly2tri.triangulation.TriangulationUtil.orient2d; +import static org.poly2tri.triangulation.TriangulationUtil.smartIncircle; +import java.util.List; +import org.poly2tri.triangulation.TriangulationMode; +import org.poly2tri.triangulation.TriangulationPoint; +import org.poly2tri.triangulation.TriangulationUtil.Orientation; +import org.poly2tri.triangulation.delaunay.DelaunayTriangle; + +/** + * Sweep-line, Constrained Delauney Triangulation (CDT) See: Domiter, V. and + * Zalik, B.(2008)'Sweep-line algorithm for constrained Delaunay triangulation', + * International Journal of Geographical Information Science + * + * "FlipScan" Constrained Edge Algorithm invented by author of this code. + * + * Author: Thomas Åhlén, thahlen@gmail.com + */ + +public class DTSweep +{ + + private final static double PI_div2 = Math.PI/2; + private final static double PI_3div4 = 3*Math.PI/4; + + public DTSweep() + {} + + /** Triangulate simple polygon with holes **/ + public static void triangulate( DTSweepContext tcx ) + { + tcx.createAdvancingFront(); + + sweep( tcx ); + + if( tcx.getTriangulationMode() == TriangulationMode.POLYGON ) + { + finalizationPolygon( tcx ); + } + else + { + finalizationConvexHull( tcx ); + } + + tcx.done(); + } + + /** + * Start sweeping the Y-sorted point set from bottom to top + * + * @param tcx + */ + private static void sweep( DTSweepContext tcx ) + { + List points; + TriangulationPoint point; + AdvancingFrontNode node; + + points = tcx.getPoints(); + + for( int i=1; i + */ + private static void turnAdvancingFrontConvex( DTSweepContext tcx, + AdvancingFrontNode b, + AdvancingFrontNode c ) + { + AdvancingFrontNode first = b; + while( c != tcx.aFront.tail ) + { + if( tcx.isDebugEnabled() ) { tcx.getDebugContext().setActiveNode( c ); } + + if( orient2d( b.point, c.point, c.next.point ) == Orientation.CCW ) + { + // [b,c,d] Concave - fill around c + fill( tcx, c ); + c = c.next; + } + else + { + // [b,c,d] Convex + if( b != first && orient2d( b.prev.point, b.point, c.point ) == Orientation.CCW ) + { + // [a,b,c] Concave - fill around b + fill( tcx, b ); + b = b.prev; + } + else + { + // [a,b,c] Convex - nothing to fill + b = c; + c = c.next; + } + } + } + } + + private static void finalizationPolygon( DTSweepContext tcx ) + { + // Get an Internal triangle to start with + DelaunayTriangle t = tcx.aFront.head.next.triangle; + TriangulationPoint p = tcx.aFront.head.next.point; + while( !t.getConstrainedEdgeCW( p ) ) + { + t = t.neighborCCW( p ); + } + + // Collect interior triangles constrained by edges + tcx.meshClean( t ); + } + + /** + * Find closes node to the left of the new point and + * create a new triangle. If needed new holes and basins + * will be filled to. + * + * @param tcx + * @param point + * @return + */ + private static AdvancingFrontNode pointEvent( DTSweepContext tcx, + TriangulationPoint point ) + { + AdvancingFrontNode node,newNode; + + node = tcx.locateNode( point ); + if( tcx.isDebugEnabled() ) { tcx.getDebugContext().setActiveNode( node ); } + newNode = newFrontTriangle( tcx, point, node ); + + // Only need to check +epsilon since point never have smaller + // x value than node due to how we fetch nodes from the front + if( point.getX() <= node.point.getX() + EPSILON ) + { + fill( tcx, node ); + } + tcx.addNode( newNode ); + + fillAdvancingFront( tcx, newNode ); + return newNode; + } + + /** + * Creates a new front triangle and legalize it + * + * @param tcx + * @param point + * @param node + * @return + */ + private static AdvancingFrontNode newFrontTriangle( DTSweepContext tcx, + TriangulationPoint point, + AdvancingFrontNode node ) + { + AdvancingFrontNode newNode; + DelaunayTriangle triangle; + + triangle = new DelaunayTriangle( point, node.point, node.next.point ); + triangle.markNeighbor( node.triangle ); + tcx.addToList( triangle ); + + newNode = new AdvancingFrontNode( point ); + newNode.next = node.next; + newNode.prev = node; + node.next.prev = newNode; + node.next = newNode; + + tcx.addNode( newNode ); // XXX: BST + + if( tcx.isDebugEnabled() ) { tcx.getDebugContext().setActiveNode( newNode ); } + + if( !legalize( tcx, triangle ) ) + { + tcx.mapTriangleToNodes( triangle ); + } + + return newNode; + } + + /** + * + * + * @param tcx + * @param edge + * @param node + */ + private static void edgeEvent( DTSweepContext tcx, + DTSweepConstraint edge, + AdvancingFrontNode node ) + { + try + { + tcx.edgeEvent.constrainedEdge = edge; + tcx.edgeEvent.right = edge.p.getX() > edge.q.getX(); + + if( tcx.isDebugEnabled() ) { tcx.getDebugContext().setPrimaryTriangle( node.triangle ); } + + if( isEdgeSideOfTriangle( node.triangle, edge.p, edge.q ) ) + { + return; + } + + // For now we will do all needed filling + // TODO: integrate with flip process might give some better performance + // but for now this avoid the issue with cases that needs both flips and fills + fillEdgeEvent( tcx, edge, node ); + + edgeEvent( tcx, edge.p, edge.q , node.triangle, edge.q ); + } + catch( PointOnEdgeException e ) + { + } + } + + private static void fillEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) + { + if( tcx.edgeEvent.right ) + { + fillRightAboveEdgeEvent( tcx, edge, node ); + } + else + { + fillLeftAboveEdgeEvent( tcx, edge, node ); + } + } + + private static void fillRightConcaveEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) + { + fill( tcx, node.next ); + if( node.next.point != edge.p ) + { + // Next above or below edge? + if( orient2d( edge.q, node.next.point, edge.p ) == Orientation.CCW ) + { + // Below + if( orient2d( node.point, node.next.point, node.next.next.point ) == Orientation.CCW ) + { + // Next is concave + fillRightConcaveEdgeEvent( tcx, edge, node ); + } + else + { + // Next is convex + } + } + } + } + + private static void fillRightConvexEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) + { + // Next concave or convex? + if( orient2d( node.next.point, node.next.next.point, node.next.next.next.point ) == Orientation.CCW ) + { + // Concave + fillRightConcaveEdgeEvent( tcx, edge, node.next ); + } + else + { + // Convex + // Next above or below edge? + if( orient2d( edge.q, node.next.next.point, edge.p ) == Orientation.CCW ) + { + // Below + fillRightConvexEdgeEvent( tcx, edge, node.next ); + } + else + { + // Above + } + } + } + + private static void fillRightBelowEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) + { + if( tcx.isDebugEnabled() ) { tcx.getDebugContext().setActiveNode( node ); } + if( node.point.getX() < edge.p.getX() ) // needed? + { + if( orient2d( node.point, node.next.point, node.next.next.point ) == Orientation.CCW ) + { + // Concave + fillRightConcaveEdgeEvent( tcx, edge, node ); + } + else + { + // Convex + fillRightConvexEdgeEvent( tcx, edge, node ); + // Retry this one + fillRightBelowEdgeEvent( tcx, edge, node ); + } + + } + } + + private static void fillRightAboveEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) + { + while( node.next.point.getX() < edge.p.getX() ) + { + if( tcx.isDebugEnabled() ) { tcx.getDebugContext().setActiveNode( node ); } + // Check if next node is below the edge + Orientation o1 = orient2d( edge.q, node.next.point, edge.p ); + if( o1 == Orientation.CCW ) + { + fillRightBelowEdgeEvent( tcx, edge, node ); + } + else + { + node = node.next; + } + } + } + + private static void fillLeftConvexEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) + { + // Next concave or convex? + if( orient2d( node.prev.point, node.prev.prev.point, node.prev.prev.prev.point ) == Orientation.CW ) + { + // Concave + fillLeftConcaveEdgeEvent( tcx, edge, node.prev ); + } + else + { + // Convex + // Next above or below edge? + if( orient2d( edge.q, node.prev.prev.point, edge.p ) == Orientation.CW ) + { + // Below + fillLeftConvexEdgeEvent( tcx, edge, node.prev ); + } + else + { + // Above + } + } + } + + private static void fillLeftConcaveEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) + { + fill( tcx, node.prev ); + if( node.prev.point != edge.p ) + { + // Next above or below edge? + if( orient2d( edge.q, node.prev.point, edge.p ) == Orientation.CW ) + { + // Below + if( orient2d( node.point, node.prev.point, node.prev.prev.point ) == Orientation.CW ) + { + // Next is concave + fillLeftConcaveEdgeEvent( tcx, edge, node ); + } + else + { + // Next is convex + } + } + } + } + + private static void fillLeftBelowEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) + { + if( tcx.isDebugEnabled() ) { tcx.getDebugContext().setActiveNode( node ); } + if( node.point.getX() > edge.p.getX() ) + { + if( orient2d( node.point, node.prev.point, node.prev.prev.point ) == Orientation.CW ) + { + // Concave + fillLeftConcaveEdgeEvent( tcx, edge, node ); + } + else + { + // Convex + fillLeftConvexEdgeEvent( tcx, edge, node ); + // Retry this one + fillLeftBelowEdgeEvent( tcx, edge, node ); + } + + } + } + + private static void fillLeftAboveEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) + { + while( node.prev.point.getX() > edge.p.getX() ) + { + if( tcx.isDebugEnabled() ) { tcx.getDebugContext().setActiveNode( node ); } + // Check if next node is below the edge + Orientation o1 = orient2d( edge.q, node.prev.point, edge.p ); + if( o1 == Orientation.CW ) + { + fillLeftBelowEdgeEvent( tcx, edge, node ); + } + else + { + node = node.prev; + } + } + } + + private static boolean isEdgeSideOfTriangle( DelaunayTriangle triangle, + TriangulationPoint ep, + TriangulationPoint eq ) + { + int index; + index = triangle.edgeIndex( ep, eq ); + if( index != -1 ) + { + triangle.markConstrainedEdge( index ); + triangle = triangle.neighbors[ index ]; + if( triangle != null ) + { + triangle.markConstrainedEdge( ep, eq ); + } + return true; + } + return false; + } + + private static void edgeEvent( DTSweepContext tcx, + TriangulationPoint ep, + TriangulationPoint eq, + DelaunayTriangle triangle, + TriangulationPoint point ) + { + TriangulationPoint p1,p2; + + if( tcx.isDebugEnabled() ) { tcx.getDebugContext().setPrimaryTriangle( triangle ); } + + if( isEdgeSideOfTriangle( triangle, ep, eq ) ) + { + return; + } + + p1 = triangle.pointCCW( point ); + Orientation o1 = orient2d( eq, p1, ep ); + if( o1 == Orientation.Collinear ) + { + if( triangle.contains( eq, p1 ) ) + { + triangle.markConstrainedEdge( eq, p1 ); + // We are modifying the constraint maybe it would be better to + // not change the given constraint and just keep a variable for the new constraint + tcx.edgeEvent.constrainedEdge.q = p1; + triangle = triangle.neighborAcross( point ); + edgeEvent( tcx, ep, p1, triangle, p1 ); + } + else + { + throw new PointOnEdgeException( "EdgeEvent - Point on constrained edge not supported yet" ); + } + return; + } + + p2 = triangle.pointCW( point ); + Orientation o2 = orient2d( eq, p2, ep ); + if( o2 == Orientation.Collinear ) + { + if( triangle.contains( eq, p2 ) ) + { + triangle.markConstrainedEdge( eq, p2 ); + // We are modifying the constraint maybe it would be better to + // not change the given constraint and just keep a variable for the new constraint + tcx.edgeEvent.constrainedEdge.q = p2; + triangle = triangle.neighborAcross( point ); + edgeEvent( tcx, ep, p2, triangle, p2 ); + } + else + { + throw new PointOnEdgeException( "EdgeEvent - Point on constrained edge not supported yet" ); + } + return; + } + + if( o1 == o2 ) + { + // Need to decide if we are rotating CW or CCW to get to a triangle + // that will cross edge + if( o1 == Orientation.CW ) + { + triangle = triangle.neighborCCW( point ); + } + else + { + triangle = triangle.neighborCW( point ); + } + edgeEvent( tcx, ep, eq, triangle, point ); + } + else + { + // This triangle crosses constraint so lets flippin start! + flipEdgeEvent( tcx, ep, eq, triangle, point ); + } + } + + private static void flipEdgeEvent( DTSweepContext tcx, + TriangulationPoint ep, + TriangulationPoint eq, + DelaunayTriangle t, + TriangulationPoint p ) + { + TriangulationPoint op, newP; + DelaunayTriangle ot; + boolean inScanArea; + + ot = t.neighborAcross( p ); + op = ot.oppositePoint( t, p ); + + if( ot == null ) + { + // If we want to integrate the fillEdgeEvent do it here + // With current implementation we should never get here + throw new RuntimeException( "[BUG:FIXME] FLIP failed due to missing triangle"); + } + + if( t.getConstrainedEdgeAcross(p) ) + { + throw new RuntimeException( "Intersecting Constraints" ); + } + + if( tcx.isDebugEnabled() ) + { + tcx.getDebugContext().setPrimaryTriangle( t ); + tcx.getDebugContext().setSecondaryTriangle( ot ); + } // TODO: remove + + inScanArea = inScanArea( p, + t.pointCCW( p ), + t.pointCW( p ), + op ); + if( inScanArea ) + { + // Lets rotate shared edge one vertex CW + rotateTrianglePair( t, p, ot, op ); + tcx.mapTriangleToNodes( t ); + tcx.mapTriangleToNodes( ot ); + + if( p == eq && op == ep ) + { + if( eq == tcx.edgeEvent.constrainedEdge.q + && ep == tcx.edgeEvent.constrainedEdge.p) + { + if( tcx.isDebugEnabled() ) { System.out.println("[FLIP] - constrained edge done" ); } // TODO: remove + t.markConstrainedEdge( ep, eq ); + ot.markConstrainedEdge( ep, eq ); + legalize( tcx, t ); + legalize( tcx, ot ); + } + else + { + if( tcx.isDebugEnabled() ) { System.out.println("[FLIP] - subedge done" ); } // TODO: remove + // XXX: I think one of the triangles should be legalized here? + } + } + else + { + if( tcx.isDebugEnabled() ) { System.out.println("[FLIP] - flipping and continuing with triangle still crossing edge" ); } // TODO: remove + Orientation o = orient2d( eq, op, ep ); + t = nextFlipTriangle( tcx, o, t, ot, p, op ); + flipEdgeEvent( tcx, ep, eq, t, p ); + } + } + else + { + newP = nextFlipPoint( ep, eq, ot, op ); + flipScanEdgeEvent( tcx, ep, eq, t, ot, newP ); + edgeEvent( tcx, ep, eq, t, p ); + } + } + + /** + * When we need to traverse from one triangle to the next we need + * the point in current triangle that is the opposite point to the next + * triangle. + * + * @param ep + * @param eq + * @param ot + * @param op + * @return + */ + private static TriangulationPoint nextFlipPoint( TriangulationPoint ep, + TriangulationPoint eq, + DelaunayTriangle ot, + TriangulationPoint op ) + { + Orientation o2d = orient2d( eq, op, ep ); + if( o2d == Orientation.CW ) + { + // Right + return ot.pointCCW( op ); + } + else if( o2d == Orientation.CCW ) + { + // Left + return ot.pointCW( op ); + } + else + { + // TODO: implement support for point on constraint edge + throw new PointOnEdgeException("Point on constrained edge not supported yet"); + } + } + + /** + * After a flip we have two triangles and know that only one will still be + * intersecting the edge. So decide which to contiune with and legalize the other + * + * @param tcx + * @param o - should be the result of an orient2d( eq, op, ep ) + * @param t - triangle 1 + * @param ot - triangle 2 + * @param p - a point shared by both triangles + * @param op - another point shared by both triangles + * @return returns the triangle still intersecting the edge + */ + private static DelaunayTriangle nextFlipTriangle( DTSweepContext tcx, + Orientation o, + DelaunayTriangle t, + DelaunayTriangle ot, + TriangulationPoint p, + TriangulationPoint op) + { + int edgeIndex; + if( o == Orientation.CCW ) + { + // ot is not crossing edge after flip + edgeIndex = ot.edgeIndex( p, op ); + ot.dEdge[edgeIndex] = true; + legalize( tcx, ot ); + ot.clearDelunayEdges(); + return t; + } + // t is not crossing edge after flip + edgeIndex = t.edgeIndex( p, op ); + t.dEdge[edgeIndex] = true; + legalize( tcx, t ); + t.clearDelunayEdges(); + return ot; + } + + /** + * Scan part of the FlipScan algorithm
+ * When a triangle pair isn't flippable we will scan for the next + * point that is inside the flip triangle scan area. When found + * we generate a new flipEdgeEvent + * + * @param tcx + * @param ep - last point on the edge we are traversing + * @param eq - first point on the edge we are traversing + * @param flipTriangle - the current triangle sharing the point eq with edge + * @param t + * @param p + */ + private static void flipScanEdgeEvent( DTSweepContext tcx, + TriangulationPoint ep, + TriangulationPoint eq, + DelaunayTriangle flipTriangle, + DelaunayTriangle t, + TriangulationPoint p ) + { + DelaunayTriangle ot; + TriangulationPoint op,newP; + boolean inScanArea; + + ot = t.neighborAcross( p ); + op = ot.oppositePoint( t, p ); + + if( ot == null ) + { + // If we want to integrate the fillEdgeEvent do it here + // With current implementation we should never get here + throw new RuntimeException( "[BUG:FIXME] FLIP failed due to missing triangle"); + } + + if( tcx.isDebugEnabled() ) + { + System.out.println("[FLIP:SCAN] - scan next point" ); // TODO: remove + tcx.getDebugContext().setPrimaryTriangle( t ); + tcx.getDebugContext().setSecondaryTriangle( ot ); + } + + inScanArea = inScanArea( eq, + flipTriangle.pointCCW( eq ), + flipTriangle.pointCW( eq ), + op ); + if( inScanArea ) + { + // flip with new edge op->eq + flipEdgeEvent( tcx, eq, op, ot, op ); + // TODO: Actually I just figured out that it should be possible to + // improve this by getting the next ot and op before the the above + // flip and continue the flipScanEdgeEvent here + // set new ot and op here and loop back to inScanArea test + // also need to set a new flipTriangle first + // Turns out at first glance that this is somewhat complicated + // so it will have to wait. + } + else + { + newP = nextFlipPoint( ep, eq, ot, op ); + flipScanEdgeEvent( tcx, ep, eq, flipTriangle, ot, newP ); + } + } + + /** + * Fills holes in the Advancing Front + * + * + * @param tcx + * @param n + */ + private static void fillAdvancingFront( DTSweepContext tcx, AdvancingFrontNode n ) + { + AdvancingFrontNode node; + double angle; + + // Fill right holes + node = n.next; + while( node.hasNext() ) + { + if( isLargeHole(node) ) + { + break; + } + fill( tcx, node ); + node = node.next; + } + + // Fill left holes + node = n.prev; + while( node.hasPrevious() ) + { + if( isLargeHole(node) ) + { + break; + } + fill( tcx, node ); + node = node.prev; + } + + // Fill right basins + if( n.hasNext() && n.next.hasNext() ) + { + angle = basinAngle( n ); + if( angle < PI_3div4 ) + { + fillBasin( tcx, n ); + } + } + } + + /** + * @param node + * @return true if hole angle exceeds 90 degrees + */ + private static boolean isLargeHole(AdvancingFrontNode node) + { + double angle = angle(node.point, node.next.point, node.prev.point); + //XXX: don't see angle being in range [-pi/2,0] due to how advancing front works +// return (angle > PI_div2) || (angle < -PI_div2); + return (angle > PI_div2) || (angle < 0); + + // ISSUE 48: http://code.google.com/p/poly2tri/issues/detail?id=48 + // TODO: Adding this fix suggested in issues 48 caused some + // triangulations to fail so commented it out for now. + // + // Also haven't been able to produce a triangulation that gives the + // problem described in issue 48. + +// AdvancingFrontNode nextNode = node.next; +// AdvancingFrontNode prevNode = node.prev; +// if( !AngleExceeds90Degrees(node.point, +// nextNode.point, +// prevNode.point)) +// { +// return false; +// } +// +// // Check additional points on front. +// AdvancingFrontNode next2Node = nextNode.next; +// // "..Plus.." because only want angles on same side as point being added. +// if( (next2Node != null) +// && !AngleExceedsPlus90DegreesOrIsNegative(node.point, +// next2Node.point, +// prevNode.point)) +// { +// return false; +// } +// +// AdvancingFrontNode prev2Node = prevNode.prev; +// // "..Plus.." because only want angles on same side as point being added. +// if( (prev2Node != null) +// && !AngleExceedsPlus90DegreesOrIsNegative(node.point, +// nextNode.point, +// prev2Node.point)) +// { +// return false; +// } +// return true; + } + +// private static boolean AngleExceeds90Degrees +// ( +// TriangulationPoint origin, +// TriangulationPoint pa, +// TriangulationPoint pb +// ) +// { +// double angle = angle(origin, pa, pb); +// return (angle > PI_div2) || (angle < -PI_div2); +// } +// +// +// private static boolean AngleExceedsPlus90DegreesOrIsNegative +// ( +// TriangulationPoint origin, +// TriangulationPoint pa, +// TriangulationPoint pb +// ) +// { +// double angle = angle(origin, pa, pb); +// return (angle > PI_div2) || (angle < 0); +// } + + /** + * Fills a basin that has formed on the Advancing Front to the right + * of given node.
+ * First we decide a left,bottom and right node that forms the + * boundaries of the basin. Then we do a reqursive fill. + * + * @param tcx + * @param node - starting node, this or next node will be left node + */ + private static void fillBasin( DTSweepContext tcx, AdvancingFrontNode node ) + { + if( orient2d( node.point, node.next.point, node.next.next.point ) == Orientation.CCW ) + { + tcx.basin.leftNode = node; + } + else + { + tcx.basin.leftNode = node.next; + } + + // Find the bottom and right node + tcx.basin.bottomNode = tcx.basin.leftNode; + while( tcx.basin.bottomNode.hasNext() + && tcx.basin.bottomNode.point.getY() >= tcx.basin.bottomNode.next.point.getY() ) + { + tcx.basin.bottomNode = tcx.basin.bottomNode.next; + } + if( tcx.basin.bottomNode == tcx.basin.leftNode ) + { + // No valid basin + return; + } + + tcx.basin.rightNode = tcx.basin.bottomNode; + while( tcx.basin.rightNode.hasNext() + && tcx.basin.rightNode.point.getY() < tcx.basin.rightNode.next.point.getY() ) + { + tcx.basin.rightNode = tcx.basin.rightNode.next; + } + if( tcx.basin.rightNode == tcx.basin.bottomNode ) + { + // No valid basins + return; + } + + tcx.basin.width = tcx.basin.rightNode.getPoint().getX() - tcx.basin.leftNode.getPoint().getX(); + tcx.basin.leftHighest = tcx.basin.leftNode.getPoint().getY() > tcx.basin.rightNode.getPoint().getY(); + + fillBasinReq( tcx, tcx.basin.bottomNode ); + } + + /** + * Recursive algorithm to fill a Basin with triangles + * + * @param tcx + * @param node - bottomNode + * @param cnt - counter used to alternate on even and odd numbers + */ + private static void fillBasinReq( DTSweepContext tcx, AdvancingFrontNode node ) + { + // if shallow stop filling + if( isShallow( tcx, node) ) + { + return; + } + + fill( tcx, node ); + if( node.prev == tcx.basin.leftNode && node.next == tcx.basin.rightNode ) + { + return; + } + else if( node.prev == tcx.basin.leftNode ) + { + Orientation o = orient2d( node.point, node.next.point, node.next.next.point ); + if( o == Orientation.CW ) + { + return; + } + node = node.next; + } + else if( node.next == tcx.basin.rightNode ) + { + Orientation o = orient2d( node.point, node.prev.point, node.prev.prev.point ); + if( o == Orientation.CCW ) + { + return; + } + node = node.prev; + } + else + { + // Continue with the neighbor node with lowest Y value + if( node.prev.point.getY() < node.next.point.getY() ) + { + node = node.prev; + } + else + { + node = node.next; + } + } + fillBasinReq( tcx, node ); + } + + private static boolean isShallow( DTSweepContext tcx, AdvancingFrontNode node ) + { + double height; + + if( tcx.basin.leftHighest ) + { + height = tcx.basin.leftNode.getPoint().getY() - node.getPoint().getY(); + } + else + { + height = tcx.basin.rightNode.getPoint().getY() - node.getPoint().getY(); + } + if( tcx.basin.width > height ) + { + return true; + } + return false; + } + + /** + * + * @param node - middle node + * @return the angle between p-a and p-b in range [-pi,pi] + */ + private static double angle( TriangulationPoint p, + TriangulationPoint a, + TriangulationPoint b ) + { + // XXX: do we really need a signed angle for holeAngle? + // could possible save some cycles here + /* Complex plane + * ab = cosA +i*sinA + * ab = (ax + ay*i)(bx + by*i) = (ax*bx + ay*by) + i(ax*by-ay*bx) + * atan2(y,x) computes the principal value of the argument function + * applied to the complex number x+iy + * Where x = ax*bx + ay*by + * y = ax*by - ay*bx + */ + final double px = p.getX(); + final double py = p.getY(); + final double ax = a.getX() - px; + final double ay = a.getY() - py; + final double bx = b.getX() - px; + final double by = b.getY() - py; + return Math.atan2( ax*by - ay*bx, ax*bx + ay*by ); + } + + /** + * The basin angle is decided against the horizontal line [1,0] + */ + private static double basinAngle( AdvancingFrontNode node ) + { + double ax = node.point.getX() - node.next.next.point.getX(); + double ay = node.point.getY() - node.next.next.point.getY(); + return Math.atan2( ay, ax ); + } + + /** + * Adds a triangle to the advancing front to fill a hole. + * @param tcx + * @param node - middle node, that is the bottom of the hole + */ + private static void fill( DTSweepContext tcx, AdvancingFrontNode node ) + { + DelaunayTriangle triangle = new DelaunayTriangle( node.prev.point, + node.point, + node.next.point ); + // TODO: should copy the cEdge value from neighbor triangles + // for now cEdge values are copied during the legalize + triangle.markNeighbor( node.prev.triangle ); + triangle.markNeighbor( node.triangle ); + tcx.addToList( triangle ); + + // Update the advancing front + node.prev.next = node.next; + node.next.prev = node.prev; + tcx.removeNode( node ); + + // If it was legalized the triangle has already been mapped + if( !legalize( tcx, triangle ) ) + { + tcx.mapTriangleToNodes( triangle ); + } + } + + /** + * Returns true if triangle was legalized + */ + private static boolean legalize( DTSweepContext tcx, + DelaunayTriangle t ) + { + int oi; + boolean inside; + TriangulationPoint p,op; + DelaunayTriangle ot; + // To legalize a triangle we start by finding if any of the three edges + // violate the Delaunay condition + for( int i=0; i<3; i++ ) + { + // TODO: fix so that cEdge is always valid when creating new triangles then we can check it here + // instead of below with ot + if( t.dEdge[i] ) + { + continue; + } + ot = t.neighbors[i]; + if( ot != null ) + { + p = t.points[i]; + op = ot.oppositePoint( t, p ); + oi = ot.index( op ); + // If this is a Constrained Edge or a Delaunay Edge(only during recursive legalization) + // then we should not try to legalize + if( ot.cEdge[oi] || ot.dEdge[oi] ) + { + t.cEdge[i] = ot.cEdge[oi]; // XXX: have no good way of setting this property when creating new triangles so lets set it here + continue; + } + inside = smartIncircle( p, + t.pointCCW( p ), + t.pointCW( p ), + op ); + if( inside ) + { + boolean notLegalized; + + // Lets mark this shared edge as Delaunay + t.dEdge[i] = true; + ot.dEdge[oi] = true; + + // Lets rotate shared edge one vertex CW to legalize it + rotateTrianglePair( t, p, ot, op ); + + // We now got one valid Delaunay Edge shared by two triangles + // This gives us 4 new edges to check for Delaunay + + // Make sure that triangle to node mapping is done only one time for a specific triangle + notLegalized = !legalize( tcx, t ); + if( notLegalized ) + { + tcx.mapTriangleToNodes( t ); + } + notLegalized = !legalize( tcx, ot ); + if( notLegalized ) + { + tcx.mapTriangleToNodes( ot ); + } + + // Reset the Delaunay edges, since they only are valid Delaunay edges + // until we add a new triangle or point. + // XXX: need to think about this. Can these edges be tried after we + // return to previous recursive level? + t.dEdge[i] = false; + ot.dEdge[oi] = false; + + // If triangle have been legalized no need to check the other edges since + // the recursive legalization will handles those so we can end here. + return true; + } + } + } + return false; + } + + /** + * Rotates a triangle pair one vertex CW + *
+     *       n2                    n2
+     *  P +-----+             P +-----+
+     *    | t  /|               |\  t |  
+     *    |   / |               | \   |
+     *  n1|  /  |n3           n1|  \  |n3
+     *    | /   |    after CW   |   \ |
+     *    |/ oT |               | oT \|
+     *    +-----+ oP            +-----+
+     *       n4                    n4
+     * 
+ */ + private static void rotateTrianglePair( DelaunayTriangle t, + TriangulationPoint p, + DelaunayTriangle ot, + TriangulationPoint op ) + { + DelaunayTriangle n1,n2,n3,n4; + n1 = t.neighborCCW( p ); + n2 = t.neighborCW( p ); + n3 = ot.neighborCCW( op ); + n4 = ot.neighborCW( op ); + + boolean ce1,ce2,ce3,ce4; + ce1 = t.getConstrainedEdgeCCW(p); + ce2 = t.getConstrainedEdgeCW(p); + ce3 = ot.getConstrainedEdgeCCW(op); + ce4 = ot.getConstrainedEdgeCW(op); + + boolean de1,de2,de3,de4; + de1 = t.getDelunayEdgeCCW(p); + de2 = t.getDelunayEdgeCW(p); + de3 = ot.getDelunayEdgeCCW(op); + de4 = ot.getDelunayEdgeCW(op); + + t.legalize( p, op ); + ot.legalize( op, p ); + + // Remap dEdge + ot.setDelunayEdgeCCW( p, de1 ); + t.setDelunayEdgeCW( p, de2 ); + t.setDelunayEdgeCCW( op, de3 ); + ot.setDelunayEdgeCW( op, de4 ); + + // Remap cEdge + ot.setConstrainedEdgeCCW( p, ce1 ); + t.setConstrainedEdgeCW( p, ce2 ); + t.setConstrainedEdgeCCW( op, ce3 ); + ot.setConstrainedEdgeCW( op, ce4 ); + + // Remap neighbors + // XXX: might optimize the markNeighbor by keeping track of + // what side should be assigned to what neighbor after the + // rotation. Now mark neighbor does lots of testing to find + // the right side. + t.clearNeighbors(); + ot.clearNeighbors(); + if( n1 != null ) ot.markNeighbor( n1 ); + if( n2 != null ) t.markNeighbor( n2 ); + if( n3 != null ) t.markNeighbor( n3 ); + if( n4 != null ) ot.markNeighbor( n4 ); + t.markNeighbor( ot ); + } +} diff --git a/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepConstraint.java b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepConstraint.java new file mode 100644 index 0000000..636cbd9 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepConstraint.java @@ -0,0 +1,103 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation.delaunay.sweep; + +import java.util.logging.Logger; +import org.poly2tri.triangulation.TriangulationConstraint; +import org.poly2tri.triangulation.TriangulationPoint; + +/** + * + * @author Thomas Åhlén, thahlen@gmail.com + * + */ +public class DTSweepConstraint extends TriangulationConstraint +{ + + public TriangulationPoint p; + public TriangulationPoint q; + + /** + * Give two points in any order. Will always be ordered so + * that q.y > p.y and q.x > p.x if same y value + * + * @param p1 + * @param p2 + */ + public DTSweepConstraint( TriangulationPoint p1, TriangulationPoint p2 ) +// throws DuplicatePointException + { + p = p1; + q = p2; + if( p1.getY() > p2.getY() ) + { + q = p1; + p = p2; + } + else if( p1.getY() == p2.getY() ) + { + if( p1.getX() > p2.getX() ) + { + q = p1; + p = p2; + } + else if( p1.getX() == p2.getX() ) + { +// throw new DuplicatePointException( p1 + "=" + p2 ); +// return; + } + } + q.addEdge(this); + } + +// public TPoint intersect( TPoint a, TPoint b ) +// { +// double pqx,pqy,bax,bay,t; +// +// pqx = p.getX()-q.getX(); +// pqy = p.getY()-q.getY(); +// t = pqy*(a.getX()-q.getX()) - pqx*(a.getY()-q.getY() ); +// t /= pqx*(b.getY()-a.getY()) - pqy*(b.getX()-a.getX()); +// bax = t*(b.getX()-a.getX()) + a.getX(); +// bay = t*(b.getY()-a.getY()) + a.getY(); +// return new TPoint( bax, bay ); +// } + + public TriangulationPoint getP() + { + return p; + } + + public TriangulationPoint getQ() + { + return q; + } +} diff --git a/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepContext.java b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepContext.java new file mode 100644 index 0000000..4605479 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepContext.java @@ -0,0 +1,280 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation.delaunay.sweep; + +import java.util.ArrayDeque; +import java.util.Collections; +import org.poly2tri.triangulation.Triangulatable; +import org.poly2tri.triangulation.TriangulationAlgorithm; +import org.poly2tri.triangulation.TriangulationConstraint; +import org.poly2tri.triangulation.TriangulationContext; +import org.poly2tri.triangulation.TriangulationPoint; +import org.poly2tri.triangulation.delaunay.DelaunayTriangle; +import org.poly2tri.triangulation.point.TPoint; + +/** + * + * @author Thomas Åhlén, thahlen@gmail.com + * + */ +public class DTSweepContext extends TriangulationContext +{ + + // Inital triangle factor, seed triangle will extend 30% of + // PointSet width to both left and right. + private final float ALPHA = 0.3f; + + /** Advancing front **/ + protected AdvancingFront aFront; + /** head point used with advancing front */ + private TriangulationPoint _head; + /** tail point used with advancing front */ + private TriangulationPoint _tail; + protected Basin basin = new Basin(); + protected EdgeEvent edgeEvent = new EdgeEvent(); + + private DTSweepPointComparator _comparator = new DTSweepPointComparator(); + + public DTSweepContext() + { + clear(); + } + + public void isDebugEnabled( boolean b ) + { + if( b ) + { + if( _debug == null ) + { + _debug = new DTSweepDebugContext(this); + } + } + _debugEnabled = b; + } + + public void removeFromList( DelaunayTriangle triangle ) + { + _triList.remove( triangle ); + // TODO: remove all neighbor pointers to this triangle +// for( int i=0; i<3; i++ ) +// { +// if( triangle.neighbors[i] != null ) +// { +// triangle.neighbors[i].clearNeighbor( triangle ); +// } +// } +// triangle.clearNeighbors(); + } + + protected void meshClean(DelaunayTriangle triangle) + { + DelaunayTriangle t1,t2; + if( triangle != null ) + { + ArrayDeque deque = new ArrayDeque(); + deque.addFirst(triangle); + triangle.isInterior(true); + + while( !deque.isEmpty() ) + { + t1 = deque.removeFirst(); + _triUnit.addTriangle( t1 ); + for( int i=0; i<3; ++i ) + { + if( !t1.cEdge[i] ) + { + t2 = t1.neighbors[i]; + if( t2 != null && !t2.isInterior() ) + { + t2.isInterior(true); + deque.addLast(t2); + } + } + } + } + } + } + + public void clear() + { + super.clear(); + _triList.clear(); + } + + public AdvancingFront getAdvancingFront() + { + return aFront; + } + + public void setHead( TriangulationPoint p1 ) { _head = p1; } + public TriangulationPoint getHead() { return _head; } + + public void setTail( TriangulationPoint p1 ) { _tail = p1; } + public TriangulationPoint getTail() { return _tail; } + + public void addNode( AdvancingFrontNode node ) + { +// System.out.println( "add:" + node.key + ":" + System.identityHashCode(node.key)); +// m_nodeTree.put( node.getKey(), node ); + aFront.addNode( node ); + } + + public void removeNode( AdvancingFrontNode node ) + { +// System.out.println( "remove:" + node.key + ":" + System.identityHashCode(node.key)); +// m_nodeTree.delete( node.getKey() ); + aFront.removeNode( node ); + } + + public AdvancingFrontNode locateNode( TriangulationPoint point ) + { + return aFront.locateNode( point ); + } + + public void createAdvancingFront() + { + AdvancingFrontNode head,tail,middle; + // Initial triangle + DelaunayTriangle iTriangle = new DelaunayTriangle( _points.get(0), + getTail(), + getHead() ); + addToList( iTriangle ); + + head = new AdvancingFrontNode( iTriangle.points[1] ); + head.triangle = iTriangle; + middle = new AdvancingFrontNode( iTriangle.points[0] ); + middle.triangle = iTriangle; + tail = new AdvancingFrontNode( iTriangle.points[2] ); + + aFront = new AdvancingFront( head, tail ); + aFront.addNode( middle ); + + // TODO: I think it would be more intuitive if head is middles next and not previous + // so swap head and tail + aFront.head.next = middle; + middle.next = aFront.tail; + middle.prev = aFront.head; + aFront.tail.prev = middle; + } + + class Basin + { + AdvancingFrontNode leftNode; + AdvancingFrontNode bottomNode; + AdvancingFrontNode rightNode; + public double width; + public boolean leftHighest; + } + + class EdgeEvent + { + DTSweepConstraint constrainedEdge; + public boolean right; + } + + /** + * Try to map a node to all sides of this triangle that don't have + * a neighbor. + * + * @param t + */ + public void mapTriangleToNodes( DelaunayTriangle t ) + { + AdvancingFrontNode n; + for( int i=0; i<3; i++ ) + { + if( t.neighbors[i] == null ) + { + n = aFront.locatePoint( t.pointCW( t.points[i] ) ); + if( n != null ) + { + n.triangle = t; + } + } + } + } + + @Override + public void prepareTriangulation( Triangulatable t ) + { + super.prepareTriangulation( t ); + + double xmax, xmin; + double ymax, ymin; + + xmax = xmin = _points.get(0).getX(); + ymax = ymin = _points.get(0).getY(); + // Calculate bounds. Should be combined with the sorting + for( TriangulationPoint p : _points ) + { + if( p.getX() > xmax ) + xmax = p.getX(); + if( p.getX() < xmin ) + xmin = p.getX(); + if( p.getY() > ymax ) + ymax = p.getY(); + if( p.getY() < ymin ) + ymin = p.getY(); + } + + double deltaX = ALPHA * ( xmax - xmin ); + double deltaY = ALPHA * ( ymax - ymin ); + TPoint p1 = new TPoint( xmax + deltaX, ymin - deltaY ); + TPoint p2 = new TPoint( xmin - deltaX, ymin - deltaY ); + + setHead( p1 ); + setTail( p2 ); + +// long time = System.nanoTime(); + // Sort the points along y-axis + Collections.sort( _points, _comparator ); +// logger.info( "Triangulation setup [{}ms]", ( System.nanoTime() - time ) / 1e6 ); + } + + + public void finalizeTriangulation() + { + _triUnit.addTriangles( _triList ); + _triList.clear(); + } + + @Override + public TriangulationConstraint newConstraint( TriangulationPoint a, TriangulationPoint b ) + { + return new DTSweepConstraint( a, b ); + } + + @Override + public TriangulationAlgorithm algorithm() + { + return TriangulationAlgorithm.DTSweep; + } +} diff --git a/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepDebugContext.java b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepDebugContext.java new file mode 100644 index 0000000..103815f --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepDebugContext.java @@ -0,0 +1,105 @@ +package org.poly2tri.triangulation.delaunay.sweep; + +import org.poly2tri.triangulation.TriangulationContext; +import org.poly2tri.triangulation.TriangulationDebugContext; +import org.poly2tri.triangulation.TriangulationPoint; +import org.poly2tri.triangulation.delaunay.DelaunayTriangle; + +public class DTSweepDebugContext extends TriangulationDebugContext +{ + /* + * Fields used for visual representation of current triangulation + */ + protected DelaunayTriangle _primaryTriangle; + protected DelaunayTriangle _secondaryTriangle; + protected TriangulationPoint _activePoint; + protected AdvancingFrontNode _activeNode; + protected DTSweepConstraint _activeConstraint; + + public DTSweepDebugContext( DTSweepContext tcx ) + { + super( tcx ); + } + + public boolean isDebugContext() + { + return true; + } + + // private Tuple2 m_circumCircle = new Tuple2( new TPoint(), new Double(0) ); +// public Tuple2 getCircumCircle() { return m_circumCircle; } + public DelaunayTriangle getPrimaryTriangle() + { + return _primaryTriangle; + } + + public DelaunayTriangle getSecondaryTriangle() + { + return _secondaryTriangle; + } + + public AdvancingFrontNode getActiveNode() + { + return _activeNode; + } + + public DTSweepConstraint getActiveConstraint() + { + return _activeConstraint; + } + + public TriangulationPoint getActivePoint() + { + return _activePoint; + } + + public void setPrimaryTriangle( DelaunayTriangle triangle ) + { + _primaryTriangle = triangle; + _tcx.update("setPrimaryTriangle"); + } + + public void setSecondaryTriangle( DelaunayTriangle triangle ) + { + _secondaryTriangle = triangle; + _tcx.update("setSecondaryTriangle"); + } + + public void setActivePoint( TriangulationPoint point ) + { + _activePoint = point; + } + + public void setActiveConstraint( DTSweepConstraint e ) + { + _activeConstraint = e; + _tcx.update("setWorkingSegment"); + } + + public void setActiveNode( AdvancingFrontNode node ) + { + _activeNode = node; + _tcx.update("setWorkingNode"); + } + + @Override + public void clear() + { + _primaryTriangle = null; + _secondaryTriangle = null; + _activePoint = null; + _activeNode = null; + _activeConstraint = null; + } + +// public void setWorkingCircumCircle( TPoint point, TPoint point2, TPoint point3 ) +// { +// double dx,dy; +// +// CircleXY.circumCenter( point, point2, point3, m_circumCircle.a ); +// dx = m_circumCircle.a.getX()-point.getX(); +// dy = m_circumCircle.a.getY()-point.getY(); +// m_circumCircle.b = Double.valueOf( Math.sqrt( dx*dx + dy*dy ) ); +// +// } +} diff --git a/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepPointComparator.java b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepPointComparator.java new file mode 100644 index 0000000..65e1754 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/DTSweepPointComparator.java @@ -0,0 +1,35 @@ +package org.poly2tri.triangulation.delaunay.sweep; + +import java.util.Comparator; + +import org.poly2tri.triangulation.TriangulationPoint; + +public class DTSweepPointComparator implements Comparator +{ + public int compare( TriangulationPoint p1, TriangulationPoint p2 ) + { + if(p1.getY() < p2.getY() ) + { + return -1; + } + else if( p1.getY() > p2.getY()) + { + return 1; + } + else + { + if(p1.getX() < p2.getX()) + { + return -1; + } + else if( p1.getX() > p2.getX() ) + { + return 1; + } + else + { + return 0; + } + } + } +} diff --git a/src/main/java/org/poly2tri/triangulation/delaunay/sweep/PointOnEdgeException.java b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/PointOnEdgeException.java new file mode 100644 index 0000000..dfc4467 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/delaunay/sweep/PointOnEdgeException.java @@ -0,0 +1,15 @@ +package org.poly2tri.triangulation.delaunay.sweep; + +public class PointOnEdgeException extends RuntimeException +{ + + /** + * + */ + private static final long serialVersionUID = 1L; + + public PointOnEdgeException( String msg ) + { + super(msg); + } +} diff --git a/src/main/java/org/poly2tri/triangulation/point/FloatBufferPoint.java b/src/main/java/org/poly2tri/triangulation/point/FloatBufferPoint.java new file mode 100644 index 0000000..ad815fc --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/point/FloatBufferPoint.java @@ -0,0 +1,94 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation.point; + +import java.nio.FloatBuffer; + +import org.poly2tri.triangulation.TriangulationPoint; + + +public class FloatBufferPoint extends TriangulationPoint +{ + private final FloatBuffer _fb; + private final int _ix,_iy,_iz; + + public FloatBufferPoint( FloatBuffer fb, int index ) + { + _fb = fb; + _ix = index; + _iy = index+1; + _iz = index+2; + } + + public final double getX() + { + return _fb.get( _ix ); + } + public final double getY() + { + return _fb.get( _iy ); + } + public final double getZ() + { + return _fb.get( _iz ); + } + + public final float getXf() + { + return _fb.get( _ix ); + } + public final float getYf() + { + return _fb.get( _iy ); + } + public final float getZf() + { + return _fb.get( _iz ); + } + + @Override + public void set( double x, double y, double z ) + { + _fb.put( _ix, (float)x ); + _fb.put( _iy, (float)y ); + _fb.put( _iz, (float)z ); + } + + public static TriangulationPoint[] toPoints( FloatBuffer fb ) + { + FloatBufferPoint[] points = new FloatBufferPoint[fb.limit()/3]; + for( int i=0,j=0; i + * A constraint defines an edge between two points in the set, these edges can not + * be crossed. They will be enforced triangle edges after a triangulation. + *

+ * + * + * @author Thomas Åhlén, thahlen@gmail.com + */ +public class ConstrainedPointSet extends PointSet +{ + int[] _index; + List _constrainedPointList = null; + + public ConstrainedPointSet( List points, int[] index ) + { + super( points ); + _index = index; + } + + /** + * + * @param points - A list of all points in PointSet + * @param constraints - Pairs of two points defining a constraint, all points must be part of given PointSet! + */ + public ConstrainedPointSet( List points, List constraints ) + { + super( points ); + _constrainedPointList = new ArrayList(); + _constrainedPointList.addAll(constraints); + } + + @Override + public TriangulationMode getTriangulationMode() + { + return TriangulationMode.CONSTRAINED; + } + + public int[] getEdgeIndex() + { + return _index; + } + + @SuppressWarnings("unchecked") + @Override + public void prepareTriangulation( TriangulationContext tcx ) + { + super.prepareTriangulation( tcx ); + if( _constrainedPointList != null ) + { + TriangulationPoint p1,p2; + Iterator iterator = _constrainedPointList.iterator(); + while(iterator.hasNext()) + { + p1 = (TriangulationPoint)iterator.next(); + p2 = (TriangulationPoint)iterator.next(); + tcx.newConstraint(p1,p2); + } + } + else + { + for( int i = 0; i < _index.length; i+=2 ) + { + // XXX: must change!! + tcx.newConstraint( _points.get( _index[i] ), _points.get( _index[i+1] ) ); + } + } + } + + /** + * TODO: TO BE IMPLEMENTED! + * Peforms a validation on given input
+ * 1. Check's if there any constraint edges are crossing or collinear
+ * 2. + * @return + */ + public boolean isValid() + { + return true; + } +} diff --git a/src/main/java/org/poly2tri/triangulation/sets/PointSet.java b/src/main/java/org/poly2tri/triangulation/sets/PointSet.java new file mode 100644 index 0000000..d4ff5b6 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/sets/PointSet.java @@ -0,0 +1,95 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation.sets; + +import java.util.ArrayList; +import java.util.List; + +import org.poly2tri.triangulation.Triangulatable; +import org.poly2tri.triangulation.TriangulationContext; +import org.poly2tri.triangulation.TriangulationMode; +import org.poly2tri.triangulation.TriangulationPoint; +import org.poly2tri.triangulation.delaunay.DelaunayTriangle; + +public class PointSet implements Triangulatable +{ + List _points; + List _triangles; + + public PointSet( List points ) + { + _points = new ArrayList(); + _points.addAll( points ); + } + + public TriangulationMode getTriangulationMode() + { + return TriangulationMode.UNCONSTRAINED; + } + + public List getPoints() + { + return _points; + } + + public List getTriangles() + { + return _triangles; + } + + public void addTriangle( DelaunayTriangle t ) + { + _triangles.add( t ); + } + + public void addTriangles( List list ) + { + _triangles.addAll( list ); + } + + public void clearTriangulation() + { + _triangles.clear(); + } + + public void prepareTriangulation( TriangulationContext tcx ) + { + if( _triangles == null ) + { + _triangles = new ArrayList( _points.size() ); + } + else + { + _triangles.clear(); + } + tcx.addPoints( _points ); + } +} diff --git a/src/main/java/org/poly2tri/triangulation/util/PointGenerator.java b/src/main/java/org/poly2tri/triangulation/util/PointGenerator.java new file mode 100644 index 0000000..baf2c17 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/util/PointGenerator.java @@ -0,0 +1,38 @@ +package org.poly2tri.triangulation.util; + +import java.util.ArrayList; +import java.util.List; + +import org.poly2tri.triangulation.TriangulationPoint; +import org.poly2tri.triangulation.point.TPoint; + +public class PointGenerator +{ + public static List uniformDistribution( int n, double scale ) + { + ArrayList points = new ArrayList(); + for( int i=0; i uniformGrid( int n, double scale ) + { + double x=0; + double size = scale/n; + double halfScale = 0.5*scale; + + ArrayList points = new ArrayList(); + for( int i=0; i scale/2 ? scale/2 : radius; + radius = radius < scale/10 ? scale/10 : radius; + } while( radius < scale/10 || radius > scale/2 ); + point = new PolygonPoint( radius*Math.cos( (PI_2*i)/vertexCount ), + radius*Math.sin( (PI_2*i)/vertexCount ) ); + points[i] = point; + } + return new Polygon( points ); + } + + public static Polygon RandomCircleSweep2( double scale, int vertexCount ) + { + PolygonPoint point; + PolygonPoint[] points; + double radius = scale/4; + + points = new PolygonPoint[vertexCount]; + for(int i=0; i scale/2 ? scale/2 : radius; + radius = radius < scale/10 ? scale/10 : radius; + } while( radius < scale/10 || radius > scale/2 ); + point = new PolygonPoint( radius*Math.cos( (PI_2*i)/vertexCount ), + radius*Math.sin( (PI_2*i)/vertexCount ) ); + points[i] = point; + } + return new Polygon( points ); + } +} diff --git a/src/main/java/org/poly2tri/triangulation/util/QuadTreeRefinement.java b/src/main/java/org/poly2tri/triangulation/util/QuadTreeRefinement.java new file mode 100644 index 0000000..f3ab2ed --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/util/QuadTreeRefinement.java @@ -0,0 +1,17 @@ +package org.poly2tri.triangulation.util; + +import org.poly2tri.geometry.polygon.Polygon; + +/** + * Use a QuadTree traversal to add steiner points + * inside the polygon that needs refinement + * + * @author thahlen@gmail.com + */ +public class QuadTreeRefinement +{ + public static final void refine( Polygon p, int depth ) + { + + } +} diff --git a/src/main/java/org/poly2tri/triangulation/util/Tuple2.java b/src/main/java/org/poly2tri/triangulation/util/Tuple2.java new file mode 100644 index 0000000..4c5fa7d --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/util/Tuple2.java @@ -0,0 +1,43 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation.util; + +public class Tuple2 +{ + public A a; + public B b; + + public Tuple2(A a,B b) + { + this.a = a; + this.b = b; + } +} diff --git a/src/main/java/org/poly2tri/triangulation/util/Tuple3.java b/src/main/java/org/poly2tri/triangulation/util/Tuple3.java new file mode 100644 index 0000000..b81d8a5 --- /dev/null +++ b/src/main/java/org/poly2tri/triangulation/util/Tuple3.java @@ -0,0 +1,45 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.poly2tri.triangulation.util; + +public class Tuple3 +{ + public A a; + public B b; + public C c; + + public Tuple3(A a,B b,C c) + { + this.a = a; + this.b = b; + this.c = c; + } +}