//---------------------------------------------------------// /// <summary> /// Combines two Geometries, offset by the specified amount. /// </summary> /// <param name="geometry1">The first geometry to combine.</param> /// <param name="offset1">The offset of the first geometry relative to the second geometry, specified in puzzle piece units.</param> /// <param name="geometry1">The second geometry to combine.</param> /// <param name="offset1">The offset of the second geometry relative to the first geometry, specified in puzzle piece units.</param> /// <returns>The combined geometry</returns> private Geometry CombineGeometries(PuzzlePiece piece1, PuzzlePiece piece2) { // Get the geometires that will be joined Geometry geometry1 = piece1.ClipShape; Geometry geometry2 = piece2.ClipShape; // Get the offset of each piece relative to the puzzle Vector offset1InPixels = GetPieceTopLeftInScreenUnits(piece1); Vector offset2InPixels = GetPieceTopLeftInScreenUnits(piece2); // Use the offsets determined above to find the offset of each piece relative to the other piece // Between the two offsets, only one offset will have a nonzero value for x and y. This could be // the same offset, or different offsets. Possible combinations are (0,0) and (100, 100); (50, 0) // and (0, 75) but not (10, 0) and (15, 0) Vector relativeOffset1 = new Vector(Math.Max(0, Math.Round(offset1InPixels.X - offset2InPixels.X)), Math.Max(0, Math.Round(offset1InPixels.Y - offset2InPixels.Y))); Vector relativeOffset2 = new Vector(Math.Max(0, Math.Round(offset2InPixels.X - offset1InPixels.X)), Math.Max(0, Math.Round(offset2InPixels.Y - offset1InPixels.Y))); // Translate the geometries according to their offsets so they don't overlap when they're joined geometry1.Transform = new TranslateTransform(relativeOffset1.X, relativeOffset1.Y); geometry2.Transform = new TranslateTransform(relativeOffset2.X, relativeOffset2.Y); // Make a new GeometryGroup GeometryGroup newGeometry = new GeometryGroup(); newGeometry.FillRule = FillRule.Nonzero; // Add the Geometries to the group newGeometry.Children.Add(geometry1); newGeometry.Children.Add(geometry2); // Flatten the group and return it return newGeometry.GetFlattenedPathGeometry(); }
//---------------------------------------------------------// /// <summary> /// Calculates the adjustment that must be made to a ScatterViewItem's center when /// its content is replaced with differently sized content so that the piece does not /// appear to jump after two pieces are joined. /// </summary> /// <param name="originalPiece">The original content of the item.</param> /// <param name="newPiece">The content that will become the item's content.</param> /// <returns>The adjustment that should be applied to that item's center, relative to the puzzle piece.</returns> public Vector CalculateJoinCenterAdjustment(PuzzlePiece originalPiece, PuzzlePiece newPiece) { Vector originalOffset = GetRelativePieceCenterInScreenUnits(originalPiece); Vector newOffset = GetRelativePieceCenterInScreenUnits(newPiece); double adjustmentX = Math.Ceiling(newOffset.X - originalOffset.X); double adjustnemtY = Math.Ceiling(newOffset.Y - originalOffset.Y); return new Vector(adjustmentX, adjustnemtY); }
//---------------------------------------------------------// /// <summary> /// Try to join two puzzle pieces together. /// </summary> /// <param name="piece1">The first of two pieces to try to join.</param> /// <param name="piece2">The second of two pieces to try to join.</param> /// <returns>The result of the join.</returns> public PuzzlePiece JoinPieces(PuzzlePiece piece1, PuzzlePiece piece2) { // Combine the viewboxes VisualBrush newBrush = new VisualBrush(piece1.ImageBrush.Visual); newBrush.Viewbox = Rect.Union(piece1.ImageBrush.Viewbox, piece2.ImageBrush.Viewbox); newBrush.ViewboxUnits = BrushMappingMode.RelativeToBoundingBox; // Combine the pieces HashSet<int> newPieces = new HashSet<int>(piece1.Pieces); newPieces.UnionWith(piece2.Pieces); // Combine the geometries Geometry newGeometry = CombineGeometries(piece1, piece2); // Now make them into a piece return new PuzzlePiece(newGeometry, newBrush, newPieces); }
//---------------------------------------------------------// /// <summary> /// Gets the center of a specific piece number in a PuzzlePiece /// </summary> /// <param name="piece">The piece number for which to find the center</param> /// <param name="pieceNumber">The piece in which the piece number exists</param> /// <returns>The center of the piece numberm in screen units</returns> private Vector GetPieceNumberCenterInScreenUnits(PuzzlePiece piece, int pieceNumber) { Vector pieceOffset = GetPieceTopLeftInPieceUnits(piece); Vector pieceNumberOffset = new Vector(GetPieceColumn(pieceNumber), GetPieceRow(pieceNumber)); Vector pieceCenter = pieceNumberOffset - pieceOffset; pieceCenter *= edgeLength; pieceCenter += halfEdge; if (pieceOffset.X != 0) { pieceCenter.X += overlap * edgeLength / 2; } if (pieceOffset.Y != 0) { pieceCenter.Y += overlap * edgeLength / 2; } return pieceCenter; }
//---------------------------------------------------------// /// <summary> /// Gets the offset between the center of two pieces. /// </summary> /// <param name="piece1">The first of the two pieces.</param> /// <param name="piece2">The second of the two pieces.</param> /// <returns>The offset between the centers of the two pieces, measured in pieces.</returns> private Vector GetRelativeCenterOffsetInPieceUnits(PuzzlePiece piece1, PuzzlePiece piece2) { return GetRelativePieceCenterInScreenUnits(piece2) - GetRelativePieceCenterInScreenUnits(piece1); }
//---------------------------------------------------------// /// <summary> /// Gets the offset from the top left of a PuzzlePiece to that same piece's center /// </summary> /// <param name="piece">The piece for which to calculate the center</param> /// <returns>The center of the piece, measured in screen units</returns> private static Vector GetAbsolutePieceCenterInScreenUnits(PuzzlePiece piece) { return new Vector(Math.Ceiling(piece.ClipShape.Bounds.Width) / 2, Math.Ceiling(piece.ClipShape.Bounds.Height) / 2); }
//---------------------------------------------------------// /// <summary> /// Gets the offset from the top left of the puzzle to the center of a PuzzlePiece. /// </summary> /// <param name="piece">The piece.</param> /// <returns>The offset from the top left of the puzzle to the center of the piece, measured in screen units.</returns> private Vector GetRelativePieceCenterInScreenUnits(PuzzlePiece piece) { Vector PieceCenter = GetAbsolutePieceCenterInScreenUnits(piece); Vector PieceOffset = GetPieceTopLeftInScreenUnits(piece); return PieceOffset + PieceCenter; }
//---------------------------------------------------------// /// <summary> /// Gets a piece's top left relative to the puzzle. /// </summary> /// <param name="piece">The puzzle piece.</param> /// <returns>A vector that reprsents the top left of the piece, measured in screen units.</returns> private Vector GetPieceTopLeftInScreenUnits(PuzzlePiece piece) { Vector pieceOffset = GetPieceTopLeftInPieceUnits(piece); return new Vector(Math.Max(0, edgeLength * (pieceOffset.X - overlap)), Math.Max(0, edgeLength * (pieceOffset.Y - overlap))); }
//---------------------------------------------------------// /// <summary> /// Gets a piece's top left relative to the puzzle. /// </summary> /// <param name="piece">The puzzle piece.</param> /// <returns>A vector that reprsents the top left of the piece, measured in pieces.</returns> private Vector GetPieceTopLeftInPieceUnits(PuzzlePiece piece) { Vector offset = new Vector(int.MaxValue, int.MaxValue); foreach (int i in piece.Pieces) { int row = GetPieceRow(i); int col = GetPieceColumn(i); if (col < offset.X) { offset.X = col; } if (row < offset.Y ) { offset.Y = row; } } return offset; }
//---------------------------------------------------------// /// <summary> /// Creates puzzle pieces from a visual, and adds them into the ScatterView. /// </summary> /// <param name="visual"></param> void LoadVisualAsPuzzle(Visual visual, Direction fromDirection) { // The more columns/rows, the less each piece needs to overlap float rowOverlap = PuzzleManager.Overlap / rowCount; float colOverlap = PuzzleManager.Overlap / colCount; puzzleBrush = new VisualBrush(visual); // Tell the puzzle manager to load a puzzle with the specified dimensions puzzleManager.LoadPuzzle(colCount, rowCount); for (int row = 0; row < rowCount; row++) { for (int column = 0; column < colCount; column++) { // Calculate the size of the rectangle that will be used to create a viewbox into the puzzle image. // The size is specified as a percentage of the total image size. float boxLeft = (float) column / (float)colCount; float boxTop = (float) row / (float)rowCount; float boxWidth = 1f / colCount; float boxHeight = 1f / rowCount; // Items in column 0 don't have any male puzzle parts on their side, all others do if (column != 0) { boxLeft -= colOverlap; boxWidth += colOverlap; } // Items in row 0 don't have any male puzzle parts on their top, all others do if (row != 0) { boxTop -= rowOverlap; boxHeight += rowOverlap; } // Make a visual brush based on the rectangle that was just calculated. VisualBrush itemBrush = new VisualBrush(visual); itemBrush.Viewbox = new Rect(boxLeft, boxTop, boxWidth, boxHeight); itemBrush.ViewboxUnits = BrushMappingMode.RelativeToBoundingBox; // Get the shape of the piece Geometry shape = GetPieceGeometry(column, row); // Put the brush into a puzzle piece PuzzlePiece piece = new PuzzlePiece( column + (colCount * row), shape, itemBrush); // Add the PuzzlePiece to a ScatterViewItem SSC.ScatterViewItem item = new SSC.ScatterViewItem(); item.Content = piece; // Set the initial size of the item and prevent it from being resized item.Width = Math.Round(piece.ClipShape.Bounds.Width, 0); item.Height = Math.Round(piece.ClipShape.Bounds.Height, 0); item.CanScale = false; // Set the item's data context so it can use the piece's shape Binding binding = new Binding(); binding.Source = piece; item.SetBinding(ScatterViewItem.DataContextProperty, binding); // Animate the item into view AddPiece(item, fromDirection); } } }