public void TestMaskWithOverlapping() { var a = new int[, ] { { 1, 0 }, { 0, 1 }, }; var model = OverlappingModel.Create(a, 2, false, 8); var mask = new bool[4 * 5]; for (var x = 0; x < 5; x++) { for (var y = 0; y < 4; y++) { if (x == 2 || x == 3) { mask[x + y * 5] = false; } else { mask[x + y * 5] = true; } } } var topology = new GridTopology(5, 4, false).WithMask(mask); var propagator = new TilePropagator(model, topology); propagator.Select(0, 0, 0, new Tile(1)); propagator.Select(4, 0, 0, new Tile(0)); propagator.Run(); Assert.AreEqual(Resolution.Decided, propagator.Status); }
public void Init(TilePropagator propagator) { var tileSet = propagator.CreateTileSet(Tiles); var point = Point ?? GetRandomPoint(propagator, tileSet); propagator.Select(point.X, point.Y, point.Z, tileSet); }
public Resolution Init(TilePropagator propagator) { var point = Point ?? GetRandomPoint(propagator); propagator.Select(point.X, point.Y, point.Z, Tile); return(Resolution.Undecided); }
public void Check(TilePropagator propagator) { var topology = propagator.Topology; var indices = topology.Width * topology.Height * topology.Depth; // Initialize couldBePath and mustBePath based on wave possibilities var couldBePath = new bool[indices]; var mustBePath = new bool[indices]; for (int i = 0; i < indices; i++) { topology.GetCoord(i, out var x, out var y, out var z); propagator.GetBannedSelected(x, y, z, tileSet, out var isBanned, out var isSelected); couldBePath[i] = !isBanned; mustBePath[i] = isSelected; } // Select relevant cells, i.e. those that must be connected. bool[] relevant; if (EndPoints == null) { relevant = mustBePath; } else { relevant = new bool[indices]; if (EndPoints.Length == 0) { return; } foreach (var endPoint in EndPoints) { var index = topology.GetIndex(endPoint.X, endPoint.Y, endPoint.Z); relevant[index] = true; } } var walkable = couldBePath; var isArticulation = PathConstraintUtils.GetArticulationPoints(graph, walkable, relevant); if (isArticulation == null) { propagator.SetContradiction(); return; } // All articulation points must be paths, // So ban any other possibilities for (var i = 0; i < indices; i++) { if (isArticulation[i]) { topology.GetCoord(i, out var x, out var y, out var z); propagator.Select(x, y, z, tileSet); } } }
public void Check(TilePropagator propagator) { var topology = propagator.Topology; var width = topology.Width; var height = topology.Height; var depth = topology.Depth; var yesCount = countTracker.YesCount; var noCount = countTracker.NoCount; var maybeCount = countTracker.MaybeCount; if (Comparison == CountComparison.AtMost || Comparison == CountComparison.Exactly) { if (yesCount > Count) { // Already got too many, just fail propagator.SetContradiction(); return; } if (yesCount == Count && maybeCount > 0) { // We've reached the limit, ban any more foreach (var index in topology.GetIndices()) { var selected = selectedChangeTracker.GetQuadstate(index); if (selected.IsMaybe()) { propagator.Topology.GetCoord(index, out var x, out var y, out var z); propagator.Ban(x, y, z, tileSet); } } } } if (Comparison == CountComparison.AtLeast || Comparison == CountComparison.Exactly) { if (yesCount + maybeCount < Count) { // Already got too few, just fail propagator.SetContradiction(); return; } if (yesCount + maybeCount == Count && maybeCount > 0) { // We've reached the limit, select all the rest foreach (var index in topology.GetIndices()) { var selected = selectedChangeTracker.GetQuadstate(index); if (selected.IsMaybe()) { propagator.Topology.GetCoord(index, out var x, out var y, out var z); propagator.Select(x, y, z, tileSet); } } } } }
public void SelectPath(int node) { var(index, dir) = Unpack(node, GetNodesPerIndex()); topology.GetCoord(index, out var x, out var y, out var z); if (dir == null) { propagator.Select(x, y, z, pathTileSet); } else { if (MustBePath[node]) { return; } if (tileSetByExit.TryGetValue((Direction)dir, out var exitTiles)) { propagator.Select(x, y, z, exitTiles); } } }
public void TestPriority() { var t1 = new Tile(1); var t2 = new Tile(2); var t3 = new Tile(3); var model = new AdjacentModel(DirectionSet.Cartesian2d); model.AddAdjacency(t1, t1, Direction.XPlus); model.AddAdjacency(t1, t2, Direction.XPlus); model.AddAdjacency(t2, t2, Direction.XPlus); model.AddAdjacency(t2, t3, Direction.XPlus); model.AddAdjacency(t3, t3, Direction.XPlus); model.SetUniformFrequency(); var topology = new GridTopology(6, 1, false).WithMask(new bool[] { true, true, true, true, true, false }); IDictionary <Tile, PriorityAndWeight> weights = new Dictionary <Tile, PriorityAndWeight> { { t1, new PriorityAndWeight { Priority = 0, Weight = 1 } }, { t2, new PriorityAndWeight { Priority = 1, Weight = 1 } }, { t3, new PriorityAndWeight { Priority = 2, Weight = 1 } }, }; var weightsArray = TopoArray.CreateByIndex(_ => weights, topology); var propagator = new TilePropagator(model, topology, new TilePropagatorOptions { IndexPickerType = IndexPickerType.ArrayPriorityMinEntropy, WeightSetByIndex = TopoArray.CreateByIndex(_ => 0, topology), WeightSets = new Dictionary <int, IDictionary <Tile, PriorityAndWeight> > { { 0, weights } }, }); propagator.Select(0, 0, 0, t1); propagator.Run(); Assert.AreEqual(Resolution.Decided, propagator.Status); var r = propagator.ToValueArray <int>(); Assert.AreEqual(1, r.Get(0, 0)); Assert.AreEqual(2, r.Get(1, 0)); Assert.AreEqual(3, r.Get(2, 0)); Assert.AreEqual(3, r.Get(3, 0)); }
public Resolution Init(TilePropagator propagator) { var width = propagator.Topology.Width; var height = propagator.Topology.Height; var depth = propagator.Topology.Depth; for (var x = 0; x < width; x++) { var xmin = x == 0; var xmax = x == width - 1; for (var y = 0; y < height; y++) { var ymin = y == 0; var ymax = y == height - 1; for (var z = 0; z < depth; z++) { var zmin = z == 0; var zmax = z == depth - 1; var match = (Match(Sides, xmin, xmax, ymin, ymax, zmin, zmax) && !Match(ExcludeSides, xmin, xmax, ymin, ymax, zmin, zmax)) != InvertArea; if (match) { if (Ban) { var cellStatus = propagator.Ban(x, y, z, Tile); if (cellStatus != Resolution.Undecided) { return(cellStatus); } } else { var cellStatus = propagator.Select(x, y, z, Tile); if (cellStatus != Resolution.Undecided) { return(cellStatus); } } } } } } return(Resolution.Undecided); }
public void TestParityConstraint() { var w = 10; var h = 10; var topology = new GridTopology(10, 10, false); var pathModel = new PathModel(forks: false); var constraint = new ParityConstraint { PathSpec = new EdgedPathSpec { Exits = pathModel.Exits }, }; var options = new TilePropagatorOptions { BackTrackDepth = -1, Constraints = new[] { constraint }, }; var propagator = new TilePropagator(pathModel.Model, topology, options); for (var x = 0; x < w; x++) { for (var y = 0; y < h; y++) { void Select(Tile t) => propagator.Select(x, y, 0, t); if (x == 0 && y == 1) { Select(pathModel.Straight2); continue; } if (x == 0 || y == 0 || x == w - 1 || y == h - 1) { Select(pathModel.Empty); } } } propagator.Step(); Assert.AreEqual(Resolution.Contradiction, propagator.Status); }
public void Init(TilePropagator propagator) { var tiles = propagator.CreateTileSet(Tiles); var width = propagator.Topology.Width; var height = propagator.Topology.Height; var depth = propagator.Topology.Depth; for (var x = 0; x < width; x++) { var xmin = x == 0; var xmax = x == width - 1; for (var y = 0; y < height; y++) { var ymin = y == 0; var ymax = y == height - 1; for (var z = 0; z < depth; z++) { var zmin = z == 0; var zmax = z == depth - 1; var match = (Match(Sides, xmin, xmax, ymin, ymax, zmin, zmax) && !Match(ExcludeSides, xmin, xmax, ymin, ymax, zmin, zmax)) != InvertArea; if (match) { if (Ban) { propagator.Ban(x, y, z, tiles); } else { propagator.Select(x, y, z, tiles); } } } } } }
public void TestDirtyIndexPicker() { var t1 = new Tile(1); var t2 = new Tile(2); var t3 = new Tile(3); var model = new AdjacentModel(DirectionSet.Cartesian2d); model.AddAdjacency(t1, t1, Direction.XPlus); model.AddAdjacency(t1, t2, Direction.XPlus); model.AddAdjacency(t2, t2, Direction.XPlus); model.AddAdjacency(t2, t3, Direction.XPlus); model.AddAdjacency(t3, t3, Direction.XPlus); model.AddAdjacency(t3, t2, Direction.XPlus); model.AddAdjacency(t2, t1, Direction.XPlus); model.SetUniformFrequency(); var topology = new GridTopology(6, 1, false); var options = new TilePropagatorOptions { IndexPickerType = IndexPickerType.Dirty, TilePickerType = TilePickerType.Ordered, CleanTiles = TopoArray.FromConstant(t1, topology), }; var propagator = new TilePropagator(model, topology, options); propagator.Select(3, 0, 0, t3); propagator.Run(); var a = propagator.ToValueArray <int?>(); Assert.AreEqual(null, a.Get(0, 0)); Assert.AreEqual(null, a.Get(1, 0)); Assert.AreEqual(2, a.Get(2, 0)); Assert.AreEqual(3, a.Get(3, 0)); Assert.AreEqual(2, a.Get(4, 0)); Assert.AreEqual(null, a.Get(5, 0)); }
public void TestToTopArray() { var a = new int[, ] { { 1, 0 }, { 0, 1 }, }; var model = OverlappingModel.Create(a, 2, false, 8); var propagator = new TilePropagator(model, new GridTopology(4, 4, false)); propagator.Select(0, 0, 0, new Tile(1)); var status = propagator.Run(); Assert.AreEqual(Resolution.Decided, status); var result = propagator.ToValueArray <int>().ToArray2d(); Assert.AreEqual(4, result.GetLength(0)); Assert.AreEqual(4, result.GetLength(1)); Assert.AreEqual(1, result[0, 0]); Assert.AreEqual(1, result[3, 3]); }
public void TestUnassignableEager() { var model = new AdjacentModel(DirectionSet.Cartesian2d); var tile1 = new Tile(1); var tile2 = new Tile(2); var tiles = new[] { tile1, tile2 }; model.AddAdjacency(tiles, tiles, Direction.XPlus); model.AddAdjacency(tiles, tiles, Direction.YPlus); model.SetUniformFrequency(); var topology = new GridTopology(3, 1, false); var count = 3; var options = new TilePropagatorOptions { Constraints = new[] { new CountConstraint { Tiles = new[] { tile1, }.ToHashSet(), Count = count, Comparison = CountComparison.Exactly, Eager = true, } } }; var propagator = new TilePropagator(model, topology, options); propagator.Select(1, 0, 0, tile2); propagator.Run(); Assert.AreEqual(Resolution.Contradiction, propagator.Status); }
public void SelectPath(int index) { propagator.Topology.GetCoord(index, out var x, out var y, out var z); propagator.Select(x, y, z, tileSet); }
public void Check(TilePropagator propagator) { var topology = propagator.Topology; var indices = topology.Width * topology.Height * topology.Depth; // TODO: This shouldn't be too hard to implement if (topology.Directions.Type != Topo.DirectionSetType.Cartesian2d) { throw new Exception("EdgedPathConstraint only supported for Cartesiant2d"); } var nodesPerIndex = topology.Directions.Count + 1; // Initialize couldBePath and mustBePath based on wave possibilities var couldBePath = new bool[indices * nodesPerIndex]; var mustBePath = new bool[indices * nodesPerIndex]; for (int i = 0; i < indices; i++) { topology.GetCoord(i, out var x, out var y, out var z); couldBePath[i * nodesPerIndex] = false; foreach (var kv in actualExits) { var tile = kv.Key; var exits = kv.Value; propagator.GetBannedSelected(x, y, z, tile, out var isBanned, out var isSelected); if (!isBanned) { couldBePath[i * nodesPerIndex] = true; foreach (var exit in exits) { couldBePath[i * nodesPerIndex + 1 + (int)exit] = true; } } } // TODO: There's probably a more efficient way to do this propagator.GetBannedSelected(x, y, z, pathTileSet, out var allIsBanned, out var allIsSelected); mustBePath[i * nodesPerIndex] = allIsSelected; } // Select relevant cells, i.e. those that must be connected. bool[] relevant; if (EndPoints == null && EndPointTiles == null) { relevant = mustBePath; } else { relevant = new bool[indices * nodesPerIndex]; var relevantCount = 0; if (EndPoints != null) { foreach (var endPoint in EndPoints) { var index = topology.GetIndex(endPoint.X, endPoint.Y, endPoint.Z); relevant[index * nodesPerIndex] = true; relevantCount++; } } if (EndPointTiles != null) { for (int i = 0; i < indices; i++) { topology.GetCoord(i, out var x, out var y, out var z); propagator.GetBannedSelected(x, y, z, endPointTileSet, out var isBanned, out var isSelected); if (isSelected) { relevant[i * nodesPerIndex] = true; relevantCount++; } } } if (relevantCount == 0) { // Nothing to do. return; } } var walkable = couldBePath; var component = EndPointTiles != null ? new bool[indices] : null; var isArticulation = PathConstraintUtils.GetArticulationPoints(graph, walkable, relevant, component); if (isArticulation == null) { propagator.SetContradiction(); return; } // All articulation points must be paths, // So ban any other possibilities for (var i = 0; i < indices; i++) { topology.GetCoord(i, out var x, out var y, out var z); if (isArticulation[i * nodesPerIndex]) { propagator.Select(x, y, z, pathTileSet); } for (var d = 0; d < topology.Directions.Count; d++) { if (isArticulation[i * nodesPerIndex + 1 + d]) { propagator.Select(x, y, z, tilesByExit[(Direction)d]); } } } // Any EndPointTiles not in the connected component aren't safe to add if (EndPointTiles != null) { for (int i = 0; i < indices; i++) { if (!component[i * nodesPerIndex]) { topology.GetCoord(i, out var x, out var y, out var z); propagator.Ban(x, y, z, endPointTileSet); } } } }
public void Init(TilePropagator propagator) { tileSet = propagator.CreateTileSet(Tiles); countTracker = new CountTracker(propagator.Topology); selectedChangeTracker = propagator.CreateSelectedChangeTracker(tileSet, countTracker); if (Eager) { // Naive implementation /* * // Pick Count random indices * var topology = propagator.Topology; * var pickedIndices = new List<int>(); * var remainingIndices = new List<int>(topology.Indicies); * for (var c = 0; c < Count; c++) * { * var pickedIndexIndex = (int)(propagator.RandomDouble() * remainingIndices.Count); * pickedIndices.Add(remainingIndices[pickedIndexIndex]); * remainingIndices[pickedIndexIndex] = remainingIndices[remainingIndices.Count - 1]; * remainingIndices.RemoveAt(remainingIndices.Count - 1); * } * // Ban or select tiles to ensure an appropriate count * if(Comparison == CountComparison.AtMost || Comparison == CountComparison.Exactly) * { * foreach (var i in remainingIndices) * { * topology.GetCoord(i, out var x, out var y, out var z); * propagator.Ban(x, y, z, tileSet); * } * } * if (Comparison == CountComparison.AtLeast || Comparison == CountComparison.Exactly) * { * foreach (var i in pickedIndices) * { * topology.GetCoord(i, out var x, out var y, out var z); * propagator.Select(x, y, z, tileSet); * } * } */ var topology = propagator.Topology; var width = topology.Width; var height = topology.Height; var depth = topology.Depth; var pickedIndices = new List <int>(); var remainingIndices = new List <int>(topology.GetIndices()); while (true) { var noCount = 0; var yesCount = 0; var maybeList = new List <int>(); for (var z = 0; z < depth; z++) { for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var index = topology.GetIndex(x, y, z); if (topology.ContainsIndex(index)) { var selected = selectedChangeTracker.GetQuadstate(index); if (selected.IsNo()) { noCount++; } if (selected.IsMaybe()) { maybeList.Add(index); } if (selected.IsYes()) { yesCount++; } } } } } var maybeCount = maybeList.Count; if (Comparison == CountComparison.AtMost) { if (yesCount > Count) { // Already got too many, just fail propagator.SetContradiction(); return; } if (yesCount == Count) { // We've reached the limit, ban any more and exit Check(propagator); return; } var pickedIndex = maybeList[(int)(propagator.RandomDouble() * maybeList.Count)]; topology.GetCoord(pickedIndex, out var x, out var y, out var z); propagator.Select(x, y, z, tileSet); } else if (Comparison == CountComparison.AtLeast || Comparison == CountComparison.Exactly) { if (yesCount + maybeCount < Count) { // Already got too few, just fail propagator.SetContradiction(); return; } if (yesCount + maybeCount == Count) { // We've reached the limit, ban any more and exit Check(propagator); return; } var pickedIndex = maybeList[(int)(propagator.RandomDouble() * maybeList.Count)]; topology.GetCoord(pickedIndex, out var x, out var y, out var z); propagator.Ban(x, y, z, tileSet); } } } }
public void VisitIndex(int index, bool[] visited) { if (visited[index]) { return; } var visitedList = new List <int>(); var parityCount = 0; (int, Direction?)? amigLocation = null; var hasMultipleAmbiguous = false; var stack = new Stack <int>(); stack.Push(index); while (stack.Count > 0) { var i = stack.Pop(); if (visited[i]) { continue; } visited[i] = true; visitedList.Add(i); var isOdd = oddPathTracker?.GetQuadstate(i) ?? Quadstate.No; // Does this tile have undefined parity in number of exits if (isOdd.IsMaybe()) { // Ambiguous if (amigLocation != null) { hasMultipleAmbiguous = true; } else { amigLocation = (i, null); } } if (isOdd.IsYes()) { parityCount++; } for (var d = 0; d < topology.DirectionsCount; d++) { var direction = (Direction)d; var qs = pathView.TrackerByExit.TryGetValue(direction, out var tracker) ? tracker.GetQuadstate(i) : Quadstate.No; if (qs.IsYes()) { parityCount++; } if (qs.IsMaybe()) { if (topology.TryMove(i, direction, out var i2)) { // Need to include this cell in the region if (!visited[i2]) { stack.Push(i2); } } else { // If there's no corresponding tile to balance this one // then this exit is free to be path or not, altering parity if (amigLocation != null) { hasMultipleAmbiguous = true; } else { amigLocation = (i, direction); } } } } } // We've now fully explored this region if (hasMultipleAmbiguous) { // There's nothing we can say about this case. return; } if (amigLocation != null) { // There's exactly one ambiguous point, so set it to ensure even parity var(ambigIndex, ambigDirection) = amigLocation.Value; topology.GetCoord(ambigIndex, out var x, out var y, out var z); if (ambigDirection == null) { if (parityCount % 2 == 0) { propagator.Ban(x, y, z, oddPathTilesSet); } else { propagator.Select(x, y, z, oddPathTilesSet); } } else { if (parityCount % 2 == 0) { propagator.Ban(x, y, z, pathView.TileSetByExit[ambigDirection.Value]); } else { propagator.Select(x, y, z, pathView.TileSetByExit[ambigDirection.Value]); } } } else { if (parityCount % 2 == 0) { // This is fine } else { // This is not fine, and there's nothing ambiguous to patch things up propagator.SetContradiction(); } } }
public virtual void Init(TilePropagator propagator) { changeTracker = propagator.CreateChangeTracker(); var p = propagator; var topology = propagator.Topology; // Ban any tiles which don't have a symmetry foreach (var i in topology.GetIndices()) { if (TryMapIndex(p, i, out var i2)) { topology.GetCoord(i, out var x, out var y, out var z); propagator.Select(x, y, z, propagator.TileModel.Tiles .Where(tile => TryMapTile(tile, out var _))); } } // Ban tiles that interact badly with their own symmetry foreach (var i in topology.GetIndices()) { if (TryMapIndex(p, i, out var i2)) { topology.GetCoord(i, out var x, out var y, out var z); if (i2 == i) { // index maps to itself, so only allow tiles that map to themselves var allowedTiles = propagator.TileModel.Tiles .Where(tile => TryMapTile(tile, out var tile2) && tile == tile2); propagator.Select(x, y, z, allowedTiles); continue; } // TODO: Support overlapped model? if (propagator.TileModel is AdjacentModel adjacentModel) { for (var d = 0; d < topology.DirectionsCount; d++) { if (topology.TryMove(i, (Direction)d, out var dest) && dest == i2) { // index maps adjacent to itself, so only allow tiles that can be placed adjacent to themselves var allowedTiles = propagator.TileModel.Tiles .Where(tile => TryMapTile(tile, out var tile2) && adjacentModel.IsAdjacent(tile, tile2, (Direction)d)) .ToList(); propagator.Select(x, y, z, allowedTiles); continue; } } } if (propagator.TileModel is GraphAdjacentModel graphAdjacentModel) { var sentinel = new Tile(new object()); for (var d = 0; d < topology.DirectionsCount; d++) { if (topology.TryMove(i, (Direction)d, out var dest, out var _, out var edgeLabel) && dest == i2) { // index maps adjacent to itself, so only allow tiles that can be placed adjacent to themselves var allowedTiles = propagator.TileModel.Tiles .Where(tile => TryMapTile(tile, out var tile2) && graphAdjacentModel.IsAdjacent(tile, tile2, edgeLabel)); propagator.Select(x, y, z, allowedTiles); continue; } } } topology.GetCoord(i2, out var x2, out var y2, out var z2); } } }
private void Check(TilePropagator propagator, bool init) { var topology = propagator.Topology; var indices = topology.IndexCount; // Initialize couldBePath and mustBePath based on wave possibilities var couldBePath = new bool[indices]; var mustBePath = new bool[indices]; for (int i = 0; i < indices; i++) { var ts = selectedTracker.GetQuadstate(i); couldBePath[i] = ts.Possible(); mustBePath[i] = ts.IsYes(); } // Select relevant cells, i.e. those that must be connected. var hasEndPoints = EndPoints != null || EndPointTiles != null; bool[] relevant; if (!hasEndPoints) { relevant = mustBePath; } else { relevant = new bool[indices]; var relevantCount = 0; if (EndPoints != null) { foreach (var endPoint in EndPoints) { var index = topology.GetIndex(endPoint.X, endPoint.Y, endPoint.Z); relevant[index] = true; relevantCount++; } } if (EndPointTiles != null) { for (int i = 0; i < indices; i++) { if (endPointSelectedTracker.IsSelected(i)) { relevant[i] = true; relevantCount++; } } } if (relevantCount == 0) { // Nothing to do. return; } } if (init) { for (int i = 0; i < indices; i++) { if (relevant[i]) { topology.GetCoord(i, out var x, out var y, out var z); propagator.Select(x, y, z, tileSet); } } } var walkable = couldBePath; var info = PathConstraintUtils.GetArticulationPoints(graph, walkable, relevant); var isArticulation = info.IsArticulation; if (info.ComponentCount > 1) { propagator.SetContradiction("Path constraint found multiple components", this); return; } // All articulation points must be paths, // So ban any other possibilities for (var i = 0; i < indices; i++) { if (isArticulation[i] && !mustBePath[i]) { topology.GetCoord(i, out var x, out var y, out var z); propagator.Select(x, y, z, tileSet); } } // Any path tiles / EndPointTiles not in the connected component aren't safe to add. if (info.ComponentCount > 0) { var component = info.Component; var actualEndPointTileSet = hasEndPoints ? endPointTileSet : tileSet; if (actualEndPointTileSet != null) { for (int i = 0; i < indices; i++) { if (component[i] == null) { topology.GetCoord(i, out var x, out var y, out var z); propagator.Ban(x, y, z, actualEndPointTileSet); } } } } }
private void Check(TilePropagator propagator, bool init) { var topology = propagator.Topology; var indices = topology.Width * topology.Height * topology.Depth; var nodesPerIndex = topology.DirectionsCount + 1; // Initialize couldBePath and mustBePath based on wave possibilities var couldBePath = new bool[indices * nodesPerIndex]; var mustBePath = new bool[indices * nodesPerIndex]; var exitMustBePath = new bool[indices * nodesPerIndex]; foreach (var kv in trackerByExit) { var exit = kv.Key; var tracker = kv.Value; for (int i = 0; i < indices; i++) { var ts = tracker.GetQuadstate(i); couldBePath[i * nodesPerIndex + 1 + (int)exit] = ts.Possible(); // Cannot put this in mustBePath these points can be disconnected, depending on topology mask exitMustBePath[i * nodesPerIndex + 1 + (int)exit] = ts.IsYes(); } } for (int i = 0; i < indices; i++) { var pathTs = pathSelectedTracker.GetQuadstate(i); couldBePath[i * nodesPerIndex] = pathTs.Possible(); mustBePath[i * nodesPerIndex] = pathTs.IsYes(); } // Select relevant cells, i.e. those that must be connected. var hasEndPoints = EndPoints != null || EndPointTiles != null; bool[] relevant; if (!hasEndPoints) { // Basically equivalent to EndPoints = pathTileSet relevant = mustBePath; } else { relevant = new bool[indices * nodesPerIndex]; var relevantCount = 0; if (EndPoints != null) { foreach (var endPoint in EndPoints) { var index = topology.GetIndex(endPoint.X, endPoint.Y, endPoint.Z); relevant[index * nodesPerIndex] = true; relevantCount++; } } if (EndPointTiles != null) { for (int i = 0; i < indices; i++) { if (endPointSelectedTracker.IsSelected(i)) { relevant[i * nodesPerIndex] = true; relevantCount++; } } } if (relevantCount == 0) { // Nothing to do. return; } } if (init) { for (int i = 0; i < indices; i++) { if (relevant[i * nodesPerIndex]) { topology.GetCoord(i, out var x, out var y, out var z); propagator.Select(x, y, z, pathTileSet); } } } var walkable = couldBePath; var info = PathConstraintUtils.GetArticulationPoints(graph, walkable, relevant); var isArticulation = info.IsArticulation; if (info.ComponentCount > 1) { propagator.SetContradiction("Edged path constraint found multiple connected components.", this); return; } // All articulation points must be paths, // So ban any other possibilities for (var i = 0; i < indices; i++) { topology.GetCoord(i, out var x, out var y, out var z); if (isArticulation[i * nodesPerIndex] && !mustBePath[i * nodesPerIndex]) { propagator.Select(x, y, z, pathTileSet); } for (var d = 0; d < topology.DirectionsCount; d++) { if (isArticulation[i * nodesPerIndex + 1 + d] && !exitMustBePath[i * nodesPerIndex + 1 + d]) { if (tilesByExit.TryGetValue((Direction)d, out var exitTiles)) { propagator.Select(x, y, z, exitTiles); } } } } // Any path tiles / EndPointTiles not in the connected component aren't safe to add. if (info.ComponentCount > 0) { var component = info.Component; var actualEndPointTileSet = hasEndPoints ? endPointTileSet : pathTileSet; if (actualEndPointTileSet != null) { for (int i = 0; i < indices; i++) { if (component[i * nodesPerIndex] == null) { topology.GetCoord(i, out var x, out var y, out var z); propagator.Ban(x, y, z, actualEndPointTileSet); } } } } }
public void Check(TilePropagator propagator) { var topology = propagator.Topology; var indices = topology.Width * topology.Height * topology.Depth; // Initialize couldBePath and mustBePath based on wave possibilities var couldBePath = new bool[indices]; var mustBePath = new bool[indices]; for (int i = 0; i < indices; i++) { var ts = selectedTracker.GetTristate(i); couldBePath[i] = ts.Possible(); mustBePath[i] = ts.IsYes(); } // Select relevant cells, i.e. those that must be connected. bool[] relevant; if (EndPoints == null && EndPointTiles == null) { relevant = mustBePath; } else { relevant = new bool[indices]; var relevantCount = 0; if (EndPoints != null) { foreach (var endPoint in EndPoints) { var index = topology.GetIndex(endPoint.X, endPoint.Y, endPoint.Z); relevant[index] = true; relevantCount++; } } if (EndPointTiles != null) { for (int i = 0; i < indices; i++) { if (endPointSelectedTracker.IsSelected(i)) { relevant[i] = true; relevantCount++; } } } if (relevantCount == 0) { // Nothing to do. return; } } var walkable = couldBePath; var component = EndPointTiles != null ? new bool[indices] : null; var isArticulation = PathConstraintUtils.GetArticulationPoints(graph, walkable, relevant, component); if (isArticulation == null) { propagator.SetContradiction(); return; } // All articulation points must be paths, // So ban any other possibilities for (var i = 0; i < indices; i++) { if (isArticulation[i] && !mustBePath[i]) { topology.GetCoord(i, out var x, out var y, out var z); propagator.Select(x, y, z, tileSet); } } // Any EndPointTiles not in the connected component aren't safe to add if (EndPointTiles != null) { for (int i = 0; i < indices; i++) { if (!component[i]) { topology.GetCoord(i, out var x, out var y, out var z); propagator.Ban(x, y, z, endPointTileSet); } } } }
public void Check(TilePropagator propagator) { var topology = propagator.Topology; var indices = topology.Width * topology.Height * topology.Depth; var nodesPerIndex = topology.DirectionsCount + 1; // Initialize couldBePath and mustBePath based on wave possibilities var couldBePath = new bool[indices * nodesPerIndex]; var mustBePath = new bool[indices * nodesPerIndex]; var exitMustBePath = new bool[indices * nodesPerIndex]; foreach (var kv in trackerByExit) { var exit = kv.Key; var tracker = kv.Value; for (int i = 0; i < indices; i++) { var ts = tracker.GetTristate(i); couldBePath[i * nodesPerIndex + 1 + (int)exit] = ts.Possible(); // Cannot put this in mustBePath these points can be disconnected, depending on topology mask exitMustBePath[i * nodesPerIndex + 1 + (int)exit] = ts.IsYes(); } } for (int i = 0; i < indices; i++) { var pathTs = pathSelectedTracker.GetTristate(i); couldBePath[i * nodesPerIndex] = pathTs.Possible(); mustBePath[i * nodesPerIndex] = pathTs.IsYes(); } // Select relevant cells, i.e. those that must be connected. bool[] relevant; if (EndPoints == null && EndPointTiles == null) { relevant = mustBePath; } else { relevant = new bool[indices * nodesPerIndex]; var relevantCount = 0; if (EndPoints != null) { foreach (var endPoint in EndPoints) { var index = topology.GetIndex(endPoint.X, endPoint.Y, endPoint.Z); relevant[index * nodesPerIndex] = true; relevantCount++; } } if (EndPointTiles != null) { for (int i = 0; i < indices; i++) { if (endPointSelectedTracker.IsSelected(i)) { relevant[i * nodesPerIndex] = true; relevantCount++; } } } if (relevantCount == 0) { // Nothing to do. return; } } var walkable = couldBePath; var component = EndPointTiles != null ? new bool[indices] : null; var isArticulation = PathConstraintUtils.GetArticulationPoints(graph, walkable, relevant, component); if (isArticulation == null) { propagator.SetContradiction(); return; } // All articulation points must be paths, // So ban any other possibilities for (var i = 0; i < indices; i++) { topology.GetCoord(i, out var x, out var y, out var z); if (isArticulation[i * nodesPerIndex] && !mustBePath[i * nodesPerIndex]) { propagator.Select(x, y, z, pathTileSet); } for (var d = 0; d < topology.DirectionsCount; d++) { if (isArticulation[i * nodesPerIndex + 1 + d] && !exitMustBePath[i * nodesPerIndex + 1 + d]) { propagator.Select(x, y, z, tilesByExit[(Direction)d]); } } } // Any EndPointTiles not in the connected component aren't safe to add if (EndPointTiles != null) { for (int i = 0; i < indices; i++) { if (!component[i * nodesPerIndex]) { topology.GetCoord(i, out var x, out var y, out var z); propagator.Ban(x, y, z, endPointTileSet); } } } }
public void Check(TilePropagator propagator) { var topology = propagator.Topology; var width = topology.Width; var height = topology.Height; var depth = topology.Depth; var noCount = 0; var yesCount = 0; var maybeCount = 0; for (var z = 0; z < depth; z++) { for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var index = topology.GetIndex(x, y, z); if (topology.ContainsIndex(index)) { var selected = propagator.GetSelectedTristate(x, y, z, tileSet); if (selected.IsNo) { noCount++; } if (selected.IsMaybe) { maybeCount++; } if (selected.IsYes) { yesCount++; } } } } } if (Comparison == CountComparison.AtMost || Comparison == CountComparison.Exactly) { if (yesCount > Count) { // Already got too many, just fail propagator.SetContradiction(); return; } if (yesCount == Count && maybeCount > 0) { // We've reached the limit, ban any more for (var z = 0; z < depth; z++) { for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var index = topology.GetIndex(x, y, z); if (topology.ContainsIndex(index)) { var selected = propagator.GetSelectedTristate(x, y, z, tileSet); if (selected.IsMaybe) { propagator.Ban(x, y, z, tileSet); } } } } } } } if (Comparison == CountComparison.AtLeast || Comparison == CountComparison.Exactly) { if (yesCount + maybeCount < Count) { // Already got too few, just fail propagator.SetContradiction(); return; } if (yesCount + maybeCount == Count && maybeCount > 0) { // We've reached the limit, select all the rest for (var z = 0; z < depth; z++) { for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var index = topology.GetIndex(x, y, z); if (topology.ContainsIndex(index)) { var selected = propagator.GetSelectedTristate(x, y, z, tileSet); if (selected.IsMaybe) { propagator.Select(x, y, z, tileSet); } } } } } } } }
public void Init(TilePropagator propagator) { // Strictly speaking, no initialization is needed. // In practise, this is useful to stop WFC from blundering // into easy avoided contradictions. var topology = propagator.Topology; var width = topology.Width; var height = topology.Height; var depth = topology.Depth; // Ban any tiles which don't have a reflection // Note we don't require the topology mask to be symmetric // So there may be some spots they are ok var reflectableTileSet = propagator.CreateTileSet(propagator.TileModel.Tiles .Where(tile => TileRotation.Rotate(tile, reflectX, out var _))); foreach (var i in topology.Indicies) { topology.GetCoord(i, out var x, out var y, out var z); var x2 = topology.Width - 1 - x; var i2 = topology.GetIndex(x2, y, z); if (topology.ContainsIndex(i2)) { propagator.Select(x, y, z, reflectableTileSet); } } // Ensure we don't pick a central tile that interacts badly // with its own reflection if (propagator.TileModel is AdjacentModel adjacentModel) { TilePropagatorTileSet symetricTileSet; if (width % 2 == 1) { // Enforce the center strip is symetric symetricTileSet = propagator.CreateTileSet(adjacentModel.Tiles .Where(tile => TileRotation.Rotate(tile, reflectX, out var otherTile) && tile == otherTile)); var x = width / 2; for (var z = 0; z < depth; z++) { for (var y = 0; y < height; y++) { var i = topology.GetIndex(x, y, z); if (topology.ContainsIndex(i)) { propagator.Select(x, y, z, symetricTileSet); } } } } else { // Enforce column left of center connect to their mirrored selves symetricTileSet = propagator.CreateTileSet(adjacentModel.Tiles .Where(tile => TileRotation.Rotate(tile, reflectX, out var otherTile) && adjacentModel.IsAdjacent(tile, otherTile, Topo.Direction.XPlus))); var x = width / 2 - 1; var x2 = width / 2; for (var z = 0; z < depth; z++) { for (var y = 0; y < height; y++) { var i = topology.GetIndex(x, y, z); var i2 = topology.GetIndex(x2, y, z); if (topology.ContainsIndex(i) && topology.ContainsIndex(i2)) { propagator.Select(x, y, z, symetricTileSet); } } } } } // TODO: Something similar for OverlappingModel }
public void TestMirrorConstraint() { var trb = new TileRotationBuilder(4, true, TileRotationTreatment.Missing); var tile1 = new Tile(1); var tile2 = new Tile(2); var tile3 = new Tile(3); var tile4 = new Tile(4); var tile5 = new Tile(5); var tiles = new[] { tile1, tile2, tile3, tile4 }; var reflectX = new Rotation(0, true); trb.Add(tile1, reflectX, tile2); trb.Add(tile3, reflectX, tile3); trb.Add(tile5, reflectX, tile5); var model = new AdjacentModel(DirectionSet.Cartesian2d); model.AddAdjacency(tiles, tiles, Direction.XPlus); model.AddAdjacency(new[] { tile5 }, tiles, Direction.XPlus); model.AddAdjacency(new[] { tile5 }, tiles, Direction.XMinus); model.SetUniformFrequency(); model.SetFrequency(tile5, 0.0); var tr = trb.Build(); var constraints = new[] { new MirrorXConstraint { TileRotation = tr } }; // tile1 reflects to tile 2 { var t2 = new GridTopology(2, 1, false); var p2 = new TilePropagator(model, t2, constraints: constraints); p2.Select(0, 0, 0, tile1); var status = p2.Run(); Assert.AreEqual(Resolution.Decided, status); Assert.AreEqual(tile2, p2.ToArray().Get(1, 0)); } // tile3 reflects to tile3 { var t2 = new GridTopology(2, 1, false); var p2 = new TilePropagator(model, t2, constraints: constraints); p2.Select(0, 0, 0, tile3); var status = p2.Run(); Assert.AreEqual(Resolution.Decided, status); Assert.AreEqual(tile3, p2.ToArray().Get(1, 0)); } // tile3 only tile that can go in a central space // (tile5 can go, but has zero frequency) // So tile3 should be selected reliably { var t2 = new GridTopology(3, 1, false); var p2 = new TilePropagator(model, t2, constraints: constraints); var status = p2.Run(); Assert.AreEqual(Resolution.Decided, status); Assert.AreEqual(tile3, p2.ToArray().Get(1, 0)); } // tile5 can be reflected, but cannot // be placed adjacent to it's own reflection { var t2 = new GridTopology(2, 1, false); var p2 = new TilePropagator(model, t2, constraints: constraints); p2.Select(0, 0, 0, tile5); var status = p2.Run(); Assert.AreEqual(Resolution.Contradiction, status); } { var t2 = new GridTopology(4, 1, false); var p2 = new TilePropagator(model, t2, constraints: constraints); p2.Select(0, 0, 0, tile5); var status = p2.Run(); Assert.AreEqual(Resolution.Decided, status); } }