Esempio n. 1
0
		/// <summary>Runs this elimination technique over the supplied puzzle state and previously computed possible numbers.</summary>
		/// <param name="state">The puzzle state.</param>
		/// <param name="possibleNumbers">The previously computed possible numbers.</param>
		/// <param name="numberOfChanges">The number of changes made by this elimination technique.</param>
		/// <returns>Whether more changes may be possible based on changes made during this execution.</returns>
		internal override bool Execute(
			PuzzleState state, bool exitEarlyWhenSoleFound, 
			FastBitArray[][] possibleNumbers, out int numberOfChanges, out bool exitedEarly)
		{
			numberOfChanges = 0;
			exitedEarly = false;

			// Eliminate impossible numbers based on numbers already set in the grid
			for (int i = 0; i < state.GridSize; i++)
			{
				for (int j = 0; j < state.GridSize; j++)
				{
					// If this cell has a value, we use it to eliminate numbers in other cells
					if (state[i, j].HasValue)
					{
						byte valueToEliminate = state[i, j].Value;

						// eliminate numbers in same row
						for (int y = 0; y < state.GridSize; y++)
						{
							if (possibleNumbers[i][y][valueToEliminate])
							{
								numberOfChanges++;
								possibleNumbers[i][y][valueToEliminate] = false;
							}
						}

						// eliminate numbers in same column
						for (int x = 0; x < state.GridSize; x++)
						{
							if (possibleNumbers[x][j][valueToEliminate])
							{
								numberOfChanges++;
								possibleNumbers[x][j][valueToEliminate] = false;
							}
						}

						// eliminate numbers in same box
						int boxStartX = (i / state.BoxSize) * state.BoxSize;
						for (int x = boxStartX; x < boxStartX + state.BoxSize; x++)
						{
							int boxStartY = (j / state.BoxSize) * state.BoxSize;
							for (int y = boxStartY; y < boxStartY + state.BoxSize; y++)
							{
								if (possibleNumbers[x][y][valueToEliminate])
								{
									numberOfChanges++;
									possibleNumbers[x][y][valueToEliminate] = false;
								}
							}
						}
					}
				}
			}

			return false;
		}
Esempio n. 2
0
		/// <summary>Runs this elimination technique over the supplied puzzle state and previously computed possible numbers.</summary>
		/// <param name="state">The puzzle state.</param>
		/// <param name="possibleNumbers">The previously computed possible numbers.</param>
		/// <param name="numberOfChanges">The number of changes made by this elimination technique.</param>
		/// <returns>Whether more changes may be possible based on changes made during this execution.</returns>
		internal override bool Execute(
			PuzzleState state, bool exitEarlyWhenSoleFound, 
			FastBitArray[][] possibleNumbers, out int numberOfChanges, out bool exitedEarly)
		{
			numberOfChanges = 0;
			exitedEarly = false;
			
			// Check each row to see if it contains the start of an xwing
			for(int row=0; row<state.GridSize; row++)
			{
				int count = 0; // used to find the two first-row members of the x-wing
				int [] foundColumns = new int[2]; // used to store the two first-row members of the x-wing

				// We'll be checking all numbers to see whether they're in an x-wing
				for(int n=0; n<state.GridSize; n++)
				{
					// Now look at every column in the row, and find the occurrences of the number.
					// For it to be a valid x-wing, it must have two and only two of the given number as a possibility.
					for(int column=0; column<state.GridSize; column++)
					{
						if (possibleNumbers[row][column][n] || (state[row,column].HasValue && state[row,column].Value == n))
						{
							count++;
							if (count <= foundColumns.Length) foundColumns[count-1] = column;
							else break;
						}
					}

					// Assuming we found a row that has two and only two cells with the number as a possibility
					if (count == 2)
					{
						// Look for another row that has the same property
						for(int subRow=row+1; subRow<state.GridSize; subRow++)
						{
							bool validXwingFound = true;
							for(int subColumn=0; subColumn<state.GridSize && validXwingFound; subColumn++)
							{
								bool isMatchingColumn = (subColumn == foundColumns[0] || subColumn == foundColumns[1]);
								bool hasPossibleNumber = possibleNumbers[subRow][subColumn][n] || (state[subRow,subColumn].HasValue && state[subRow,subColumn].Value == n);
								if ((hasPossibleNumber && !isMatchingColumn) ||
									(!hasPossibleNumber && isMatchingColumn)) validXwingFound = false;
							}

							// If another row is found that has two and only two cells with the number
							// as a possibility, and if those two cells are in the same two columns
							// as the original row, woo hoo, we've got an x-wing, and we can eliminate
							// that number from every other cell in the columns containing the numbers.
							if (validXwingFound)
							{
								for(int elimRow=0; elimRow<state.GridSize; elimRow++)
								{
									if (elimRow != row && elimRow != subRow)
									{
										for(int locationNum=0; locationNum<2; locationNum++)
										{
											if (possibleNumbers[elimRow][foundColumns[locationNum]][n])
											{
												possibleNumbers[elimRow][foundColumns[locationNum]][n] = false;
												numberOfChanges++;
												if (exitEarlyWhenSoleFound && 
													possibleNumbers[elimRow][foundColumns[locationNum]].CountSet == 1)
												{
													exitedEarly = true;
													return false;
												}
											}
										}
									}
								}
								break;
							}
						}
					}
				}
			}

			return numberOfChanges != 0;
		}
Esempio n. 3
0
		/// <summary>Runs this elimination technique over the supplied puzzle state and previously computed possible numbers.</summary>
		/// <param name="state">The puzzle state.</param>
		/// <param name="exitEarlyWhenSoleFound">Whether the method can exit early when a cell with only one possible number is found.</param>
		/// <param name="possibleNumbers">The previously computed possible numbers.</param>
		/// <param name="numberOfChanges">The number of changes made by this elimination technique.</param>
		/// <param name="exitedEarly">Whether the method exited early due to a cell with only one value being found.</param>
		/// <returns>Whether more changes may be possible based on changes made during this execution.</returns>
		internal override bool Execute(
			PuzzleState state, bool exitEarlyWhenSoleFound, 
			FastBitArray[][] possibleNumbers, out int numberOfChanges, out bool exitedEarly)
		{
			numberOfChanges = 0;
			exitedEarly = false;
			byte gridSize = state.GridSize;
			byte boxSize = state.BoxSize;
			
			// For each number that can exist in the puzzle (0-8, etc.)
			for (byte n = 0; n < gridSize; n++)
			{
				// For each row, if number only exists as a possibility in one cell, set it.
				for (byte x = 0; x < gridSize; x++)
				{
					int? seenIndex = null;
					for (byte y = 0; y < gridSize; y++)
					{
						if (possibleNumbers[x][y][n])
						{
							// If this is the first time we're seeing the number, remember
							// where we're seeing it
							if (!seenIndex.HasValue) seenIndex = y;
								// We've seen this number before, so move on
							else
							{
								seenIndex = null;
								break;
							}
						}
					}
					if (seenIndex.HasValue && possibleNumbers[x][seenIndex.Value].CountSet > 1)
					{
						possibleNumbers[x][seenIndex.Value].SetAll(false);
						possibleNumbers[x][seenIndex.Value][n] = true;
						numberOfChanges++;
						if (exitEarlyWhenSoleFound)
						{
							exitedEarly = true;
							return false;
						}
					}
				}
			
				// For each column, if number only exists as a possibility in one cell, set it.
				// Same basic logic as above.
				for (byte y = 0; y < gridSize; y++)
				{
					int? seenIndex = null;
					for (byte x = 0; x < gridSize; x++)
					{
						if (possibleNumbers[x][y][n])
						{
							if (!seenIndex.HasValue) seenIndex = x;
							else
							{
								seenIndex = null;
								break;
							}
						}
					}
					if (seenIndex.HasValue && possibleNumbers[seenIndex.Value][y].CountSet > 1)
					{
						possibleNumbers[seenIndex.Value][y].SetAll(false);
						possibleNumbers[seenIndex.Value][y][n] = true;
						numberOfChanges++;
						if (exitEarlyWhenSoleFound)
						{
							exitedEarly = true;
							return false;
						}
					}
				}
			
				// For each grid, if number only exists as a possibility in one cell, set it.
				// Same basic logic as above.
				for (byte gridNum = 0; gridNum < gridSize; gridNum++)
				{
					byte gridX = (byte)(gridNum % boxSize);
					byte gridY = (byte)(gridNum / boxSize);
			
					byte startX = (byte)(gridX * boxSize);
					byte startY = (byte)(gridY * boxSize);
			
					bool canEliminate = true;
					Point? seenIndex = null;
					for (byte x = startX; x < startX + boxSize && canEliminate; x++)
					{
						for (byte y = startY; y < startY + boxSize; y++)
						{
							if (possibleNumbers[x][y][n])
							{
								if (!seenIndex.HasValue) seenIndex = new Point(x, y);
								else
								{
									canEliminate = false;
									seenIndex = null;
									break;
								}
							}
						}
					}
			
					if (seenIndex.HasValue && canEliminate && 
						possibleNumbers[seenIndex.Value.X][seenIndex.Value.Y].CountSet > 1)
					{
						possibleNumbers[seenIndex.Value.X][seenIndex.Value.Y].SetAll(false);
						possibleNumbers[seenIndex.Value.X][seenIndex.Value.Y][n] = true;
						numberOfChanges++;
						if (exitEarlyWhenSoleFound)
						{
							exitedEarly = true;
							return false;
						}
					}
				}
			}

			return numberOfChanges != 0;
		}
        /// <summary>Analyzes the state of the puzzle to determine whether it is a solution or not.</summary>
        /// <returns>The status of the puzzle.</returns>
        private PuzzleStatus AnalyzeSolutionStatus()
        {
            // Need a way of keeping track of what numbers have been used (in each row, column, box, etc.)
            // A bit array is a great way to do this, where each bit corresponds to a true/false value
            // as to whether a number was already used in a particular scenario.
            FastBitArray numbersUsed = new FastBitArray(_gridSize);

            // Make sure every column contains the right numbers.  It's ok if a column has holes
            // as long as those cells have possibilities, in which case it's a puzzle in progress.
            // However, two numbers can't be used in the same column, even if there are holes.
            for (int i = 0; i < _gridSize; i++)
            {
                numbersUsed.SetAll(false);
                for (int j = 0; j < _gridSize; j++)
                {
                    if (_grid[i, j].HasValue)
                    {
                        int value = _grid[i, j].Value;
                        if (numbersUsed[value])
                        {
                            return(PuzzleStatus.CannotBeSolved);
                        }
                        numbersUsed[value] = true;
                    }
                }
            }

            // Same for rows
            for (int j = 0; j < _gridSize; j++)
            {
                numbersUsed.SetAll(false);
                for (int i = 0; i < _gridSize; i++)
                {
                    if (_grid[i, j].HasValue)
                    {
                        int value = _grid[i, j].Value;
                        if (numbersUsed[value])
                        {
                            return(PuzzleStatus.CannotBeSolved);
                        }
                        numbersUsed[value] = true;
                    }
                }
            }

            // Same for boxes
            for (int boxNumber = 0; boxNumber < _gridSize; boxNumber++)
            {
                numbersUsed.SetAll(false);
                int boxStartX = (boxNumber / _boxSize) * _boxSize;
                for (int x = boxStartX; x < boxStartX + _boxSize; x++)
                {
                    int boxStartY = (boxNumber % _boxSize) * _boxSize;
                    for (int y = boxStartY; y < boxStartY + _boxSize; y++)
                    {
                        if (_grid[x, y].HasValue)
                        {
                            int value = _grid[x, y].Value;
                            if (numbersUsed[value])
                            {
                                return(PuzzleStatus.CannotBeSolved);
                            }
                            numbersUsed[value] = true;
                        }
                    }
                }
            }

            // Now figure out whether this is a solved puzzle or a work in progress
            // based on whether there are any holes
            for (int i = 0; i < _gridSize; i++)
            {
                for (int j = 0; j < _gridSize; j++)
                {
                    if (!_grid[i, j].HasValue)
                    {
                        return(PuzzleStatus.InProgress);
                    }
                }
            }

            // If we made it this far, this state is a valid solution!  Woo hoo!
            return(PuzzleStatus.Solved);
        }
Esempio n. 5
0
        /// <summary>Performs hidden subset elimination on one dimension of possible numbers.</summary>
        /// <param name="possibleNumbers">The row/column/box to analyze.</param>
        /// <returns>The number of changes that were made to the possible numbers.</returns>
        private int EliminateHiddenSubsets(FastBitArray [] possibleNumbers,
                                           bool exitEarlyWhenSoleFound, out bool exitedEarly)
        {
            int changesMade = 0;

            exitedEarly = false;
            int numLocations;

            int [] foundLocations = _foundLocations;             // optimization, rather than allocating on each call

            // We'll look starting with each cell in the row/column/box
            for (int i = 0; i < possibleNumbers.Length; i++)
            {
                // Only look at the cell if it has at least subsetSize values set,
                // otherwise it can't be part of a hidden subset
                int numPossible = possibleNumbers[i].CountSet;
                if (numPossible >= _subsetSize)
                {
                    // For each combination
                    foreach (int [] combination in CreateCombinations(_subsetSize, possibleNumbers[i].GetSetBits()))
                    {
                        // Find other cells that contain that same combination,
                        // but only up to the subset size
                        numLocations = 0;
                        foundLocations[numLocations++] = i;
                        for (int j = i + 1; j < possibleNumbers.Length && numLocations < foundLocations.Length; j++)
                        {
                            if (AllAreSet(combination, possibleNumbers[j]))
                            {
                                foundLocations[numLocations++] = j;
                            }
                        }

                        if (numLocations == foundLocations.Length)
                        {
                            bool isValidHidden = true;

                            // Make sure that none of the numbers appear in any other cell
                            for (int j = 0; j < possibleNumbers.Length && isValidHidden; j++)
                            {
                                bool isFoundLocation = Array.BinarySearch(foundLocations, j) >= 0;
                                if (!isFoundLocation && AnyAreSet(combination, possibleNumbers[j]))
                                {
                                    isValidHidden = false;
                                    break;
                                }
                            }

                            // If this is a valid hidden subset, eliminate all other numbers
                            // from each cell in the subset
                            if (isValidHidden)
                            {
                                foreach (int foundLoc in foundLocations)
                                {
                                    FastBitArray possibleNumbersForLoc = possibleNumbers[foundLoc];
                                    foreach (int n in possibleNumbersForLoc.GetSetBits())
                                    {
                                        if (Array.BinarySearch(combination, n) < 0)
                                        {
                                            possibleNumbersForLoc[n] = false;
                                            changesMade++;
                                        }
                                    }
                                    if (exitEarlyWhenSoleFound &&
                                        possibleNumbersForLoc.CountSet == 1)
                                    {
                                        exitedEarly = true;
                                        return(changesMade);
                                    }
                                }
                                break;
                            }
                        }
                    }
                }
            }

            return(changesMade);
        }
Esempio n. 6
0
        public static AssetSprite.CollisionMaskInfo GetInfoForSprite(ProjectFile pf, GMSprite spr, out List <Bitmap> bitmaps, bool suggestPrecise = false)
        {
            bitmaps = new List <Bitmap>(spr.TextureItems.Count);

            var info = new AssetSprite.CollisionMaskInfo
            {
                Mode = (MaskMode)spr.BBoxMode
            };

            if (spr.SepMasks == GMSprite.SepMaskType.AxisAlignedRect)
            {
                info.Type = MaskType.Rectangle;
            }
            else if (spr.SepMasks == GMSprite.SepMaskType.RotatedRect)
            {
                info.Type = MaskType.RectangleWithRotation;
            }

            // Some basic conditions to bail
            if (spr.CollisionMasks.Count != 1 && spr.CollisionMasks.Count != spr.TextureItems.Count)
            {
                return(info);
            }
            if (spr.CollisionMasks.Count == 0)
            {
                return(info);
            }

            // Get bitmaps from frames
            bitmaps = GetBitmaps(pf, spr.Width, spr.Height, spr.TextureItems);
            List <BitmapData> bitmapData = new List <BitmapData>(bitmaps.Count);

            foreach (var item in bitmaps)
            {
                bitmapData.Add(item.BasicLockBits());
            }

            int boundLeft   = Math.Clamp(spr.MarginLeft, 0, spr.Width),
                boundRight  = Math.Clamp(spr.MarginRight, 0, spr.Width - 1),
                boundTop    = Math.Clamp(spr.MarginTop, 0, spr.Height),
                boundBottom = Math.Clamp(spr.MarginBottom, 0, spr.Height - 1);

            switch (spr.SepMasks)
            {
            case GMSprite.SepMaskType.AxisAlignedRect:
            case GMSprite.SepMaskType.RotatedRect:
                switch (info.Mode)
                {
                case MaskMode.Automatic:
                    // Scan for the lowest alpha value in the bounding box
                    // When comparing each pixel, compare to the one in that spot with the highest alpha in every frame

                    bool foundNonzero = false;
                    byte lowest       = 0;
                    byte highest      = 0;

                    int stride = ((spr.Width + 7) / 8) * 8;

                    FastBitArray mask         = new FastBitArray(spr.CollisionMasks[0].Memory.Span);
                    int          strideFactor = boundTop * stride;

                    for (int y = boundTop; y <= boundBottom; y++)
                    {
                        for (int x = boundLeft; x <= boundRight; x++)
                        {
                            if (mask.GetReverse(x + strideFactor))
                            {
                                byte highestAlpha = GetHighestAlphaAt(bitmapData, x, y);
                                if (highestAlpha > highest)
                                {
                                    highest = highestAlpha;
                                }
                                if (highestAlpha != 0 && (!foundNonzero || highestAlpha < lowest))
                                {
                                    lowest       = highestAlpha;
                                    foundNonzero = true;
                                }
                            }
                        }

                        strideFactor += stride;
                    }

                    if (foundNonzero)
                    {
                        if (lowest == highest)
                        {
                            lowest = 0;         // Could be anything
                        }
                        else
                        {
                            --lowest;
                        }
                    }
                    info.AlphaTolerance = lowest;
                    break;

                case MaskMode.Manual:
                    info.Left   = spr.MarginLeft;
                    info.Right  = spr.MarginRight;
                    info.Top    = spr.MarginTop;
                    info.Bottom = spr.MarginBottom;
                    break;
                }
                break;

            case GMSprite.SepMaskType.Precise:
            {
                int stride = ((spr.Width + 7) / 8) * 8;

                bool foundNonzero = false;
                byte lowest       = 0;
                byte highest      = 0;

                if (spr.CollisionMasks.Count > 1 && spr.CollisionMasks.Count == spr.TextureItems.Count)
                {
                    info.Type = MaskType.PrecisePerFrame;

                    unsafe
                    {
                        for (int i = 0; i < spr.CollisionMasks.Count; i++)
                        {
                            BitmapData   item         = bitmapData[i];
                            FastBitArray mask         = new FastBitArray(spr.CollisionMasks[i].Memory.Span);
                            int          strideFactor = boundTop * stride;
                            for (int y = boundTop; y <= boundBottom; y++)
                            {
                                for (int x = boundLeft; x <= boundRight; x++)
                                {
                                    if (mask.GetReverse(x + strideFactor))
                                    {
                                        byte val = *((byte *)item.Scan0 + (x * 4) + (y * item.Stride) + 3);
                                        if (val > highest)
                                        {
                                            highest = val;
                                        }
                                        if (val != 0 && (!foundNonzero || val < lowest))
                                        {
                                            lowest       = val;
                                            foundNonzero = true;
                                        }
                                    }
                                }

                                strideFactor += stride;
                            }
                        }
                    }
                }
                else
                {
                    info.Type = MaskType.Precise;

                    // Scan for highest alpha, as well as diamond/ellipses
                    FastBitArray mask = new FastBitArray(spr.CollisionMasks[0].Memory.Span);

                    bool  isDiamond = true, isEllipse = true;
                    float centerX = ((spr.MarginLeft + spr.MarginRight) / 2);
                    float centerY = ((spr.MarginTop + spr.MarginBottom) / 2);
                    float radiusX = centerX - spr.MarginLeft + 0.5f;
                    float radiusY = centerY - spr.MarginTop + 0.5f;

                    int strideFactor = boundTop * stride;

                    if (!suggestPrecise && radiusX > 0f && radiusY > 0f)
                    {
                        for (int y = boundTop; y <= boundBottom; y++)
                        {
                            for (int x = boundLeft; x <= boundRight; x++)
                            {
                                float normalX   = (x - centerX) / radiusX;
                                float normalY   = (y - centerY) / radiusY;
                                bool  inDiamond = Math.Abs(normalX) + Math.Abs(normalY) <= 1f;
                                bool  inEllipse = Math.Pow(normalX, 2.0d) + Math.Pow(normalY, 2.0d) <= 1.0d;

                                if (mask.GetReverse(x + strideFactor))
                                {
                                    isDiamond &= inDiamond;
                                    isEllipse &= inEllipse;

                                    byte highestAlpha = GetHighestAlphaAt(bitmapData, x, y);
                                    if (highestAlpha > highest)
                                    {
                                        highest = highestAlpha;
                                    }
                                    if (highestAlpha != 0 && (!foundNonzero || highestAlpha < lowest))
                                    {
                                        lowest       = highestAlpha;
                                        foundNonzero = true;
                                    }
                                }
                                // Can't eliminate based on this, they can be split into pieces with multiple frames
                                //else
                                //{
                                //    isDiamond &= !inDiamond;
                                //    isEllipse &= !inEllipse;
                                //}
                            }

                            strideFactor += stride;
                        }
                    }
                    else
                    {
                        // Version without diamond/ellipse checks
                        isDiamond = false;
                        isEllipse = false;

                        for (int y = boundTop; y <= boundBottom; y++)
                        {
                            for (int x = boundLeft; x <= boundRight; x++)
                            {
                                if (mask.GetReverse(x + strideFactor))
                                {
                                    byte highestAlpha = GetHighestAlphaAt(bitmapData, x, y);
                                    if (highestAlpha > highest)
                                    {
                                        highest = highestAlpha;
                                    }
                                    if (highestAlpha != 0 && (!foundNonzero || highestAlpha < lowest))
                                    {
                                        lowest       = highestAlpha;
                                        foundNonzero = true;
                                    }
                                }
                            }

                            strideFactor += stride;
                        }
                    }

                    if (isDiamond)
                    {
                        info.Type = MaskType.Diamond;
                    }
                    else if (isEllipse)
                    {
                        info.Type = MaskType.Ellipse;
                    }
                }

                if (info.Mode == MaskMode.Manual ||
                    (info.Mode == MaskMode.Automatic && info.Type != MaskType.Precise && info.Type != MaskType.PrecisePerFrame))
                {
                    info.Left   = spr.MarginLeft;
                    info.Right  = spr.MarginRight;
                    info.Top    = spr.MarginTop;
                    info.Bottom = spr.MarginBottom;
                }

                if (info.Mode == MaskMode.Automatic || info.Type == MaskType.Precise ||
                    (info.Mode == MaskMode.Manual && info.Type == MaskType.PrecisePerFrame))
                {
                    if (foundNonzero)
                    {
                        if (lowest == highest)
                        {
                            lowest = 0;         // Could be anything
                        }
                        else
                        {
                            --lowest;
                        }
                    }
                    info.AlphaTolerance = lowest;
                }
            }
            break;
            }

            for (int i = 0; i < bitmaps.Count; i++)
            {
                bitmaps[i].UnlockBits(bitmapData[i]);
            }

            return(info);
        }
Esempio n. 7
0
        public static List <BufferRegion> GetMasksForSprite(ProjectFile pf, AssetSprite spr, out Rect maskbbox, List <Bitmap> bitmaps = null)
        {
            if (bitmaps == null)
            {
                bitmaps = GetBitmaps(pf, spr.Width, spr.Height, spr.TextureItems);
            }

            if (bitmaps.Count == 0)
            {
                maskbbox = new Rect(0, 0, 0, 0);
                return(new List <BufferRegion>());
            }

            var info = spr.CollisionMask;

            if (info.Left == null || info.Top == null || info.Right == null || info.Bottom == null)
            {
                maskbbox = new Rect(spr.Width - 1, spr.Height - 1, 0, 0);
            }
            else
            {
                maskbbox = null;
            }

            if (spr.CollisionMask.Type == MaskType.PrecisePerFrame)
            {
                // Get masks for individual frames
                List <BufferRegion> res = new List <BufferRegion>(bitmaps.Count);
                for (int i = 0; i < bitmaps.Count; i++)
                {
                    res.Add(new BufferRegion(GetMaskForBitmap(bitmaps[i], spr, ref maskbbox).ToByteArray()));
                }

                if (maskbbox != null)
                {
                    maskbbox.Left   = Math.Max(0, maskbbox.Left);
                    maskbbox.Top    = Math.Max(0, maskbbox.Top);
                    maskbbox.Right  = Math.Min(spr.Width - 1, maskbbox.Right);
                    maskbbox.Bottom = Math.Min(spr.Height - 1, maskbbox.Bottom);
                }
                return(res);
            }
            else
            {
                // Get the mask for the first frame, then add following frames
                FastBitArray mask = GetMaskForBitmap(bitmaps[0], spr, ref maskbbox);
                for (int i = 1; i < bitmaps.Count; i++)
                {
                    GetMaskForBitmap(bitmaps[i], spr, ref maskbbox, mask);
                }

                if (maskbbox != null)
                {
                    maskbbox.Left   = Math.Max(0, maskbbox.Left);
                    maskbbox.Top    = Math.Max(0, maskbbox.Top);
                    maskbbox.Right  = Math.Min(spr.Width - 1, maskbbox.Right);
                    maskbbox.Bottom = Math.Min(spr.Height - 1, maskbbox.Bottom);
                }
                return(new List <BufferRegion> {
                    new BufferRegion(mask.ToByteArray())
                });
            }
        }
Esempio n. 8
0
        public static unsafe FastBitArray GetMaskForBitmap(Bitmap bmp, AssetSprite spr, ref Rect maskbbox, FastBitArray existingMask = null)
        {
            int          stride = ((spr.Width + 7) / 8) * 8;
            FastBitArray res    = existingMask ?? new FastBitArray(stride * spr.Height);

            Rect bbox = GetBBoxForBitmap(bmp, spr);

            var info = spr.CollisionMask;
            int sprLeft, sprTop, sprRight, sprBottom;

            // Word of note: There's a lot of copies of nearly the same code here. This is to reduce the number of conditions.

            int strideFactor = bbox.Top * stride;

            switch (info.Type)
            {
            case MaskType.Rectangle:
            case MaskType.RectangleWithRotation:
                if (maskbbox != null)
                {
                    for (int y = bbox.Top; y <= bbox.Bottom; y++)
                    {
                        for (int x = bbox.Left; x <= bbox.Right; x++)
                        {
                            res.SetTrueReverse(x + strideFactor);
                            if (x < maskbbox.Left)
                            {
                                maskbbox.Left = x;
                            }
                            if (y < maskbbox.Top)
                            {
                                maskbbox.Top = y;
                            }
                            if (x > maskbbox.Right)
                            {
                                maskbbox.Right = x;
                            }
                            if (y > maskbbox.Bottom)
                            {
                                maskbbox.Bottom = y;
                            }
                        }

                        strideFactor += stride;
                    }
                }
                else
                {
                    for (int y = bbox.Top; y <= bbox.Bottom; y++)
                    {
                        for (int x = bbox.Left; x <= bbox.Right; x++)
                        {
                            res.SetTrueReverse(x + strideFactor);
                        }

                        strideFactor += stride;
                    }
                }
                break;

            case MaskType.Precise:
            case MaskType.PrecisePerFrame:
                int tolerance = info.AlphaTolerance ?? 0;
                var data      = bmp.BasicLockBits();
                unsafe
                {
                    byte *ptr = (byte *)data.Scan0;
                    if (maskbbox != null)
                    {
                        for (int y = bbox.Top; y <= bbox.Bottom; y++)
                        {
                            for (int x = bbox.Left; x <= bbox.Right; x++)
                            {
                                if (*(ptr + (x * 4) + (y * data.Stride) + 3) > tolerance)
                                {
                                    res.SetTrueReverse(x + strideFactor);
                                    if (x < maskbbox.Left)
                                    {
                                        maskbbox.Left = x;
                                    }
                                    if (y < maskbbox.Top)
                                    {
                                        maskbbox.Top = y;
                                    }
                                    if (x > maskbbox.Right)
                                    {
                                        maskbbox.Right = x;
                                    }
                                    if (y > maskbbox.Bottom)
                                    {
                                        maskbbox.Bottom = y;
                                    }
                                }
                            }

                            strideFactor += stride;
                        }
                    }
                    else
                    {
                        for (int y = bbox.Top; y <= bbox.Bottom; y++)
                        {
                            for (int x = bbox.Left; x <= bbox.Right; x++)
                            {
                                if (*(ptr + (x * 4) + (y * data.Stride) + 3) > tolerance)
                                {
                                    res.SetTrueReverse(x + strideFactor);
                                }
                            }

                            strideFactor += stride;
                        }
                    }
                }
                bmp.UnlockBits(data);
                break;

            case MaskType.Diamond:
            {
                if (info.Mode == MaskMode.FullImage)
                {
                    sprLeft   = 0;
                    sprTop    = 0;
                    sprRight  = spr.Width - 1;
                    sprBottom = spr.Height - 1;
                }
                else
                {
                    sprLeft   = (int)info.Left;
                    sprTop    = (int)info.Top;
                    sprRight  = (int)info.Right;
                    sprBottom = (int)info.Bottom;
                }

                float centerX = (sprLeft + sprRight) / 2;
                float centerY = (sprTop + sprBottom) / 2;
                float radiusX = centerX - sprLeft + 0.5f;
                float radiusY = centerY - sprTop + 0.5f;

                if (radiusX <= 0 || radiusY <= 0)
                {
                    break;
                }

                if (maskbbox != null)
                {
                    for (int y = bbox.Top; y <= bbox.Bottom; y++)
                    {
                        for (int x = bbox.Left; x <= bbox.Right; x++)
                        {
                            float normalX = (x - centerX) / radiusX;
                            float normalY = (y - centerY) / radiusY;
                            if (Math.Abs(normalX) + Math.Abs(normalY) <= 1f)
                            {
                                res.SetTrueReverse(x + strideFactor);
                                if (x < maskbbox.Left)
                                {
                                    maskbbox.Left = x;
                                }
                                if (y < maskbbox.Top)
                                {
                                    maskbbox.Top = y;
                                }
                                if (x > maskbbox.Right)
                                {
                                    maskbbox.Right = x;
                                }
                                if (y > maskbbox.Bottom)
                                {
                                    maskbbox.Bottom = y;
                                }
                            }
                        }

                        strideFactor += stride;
                    }
                }
                else
                {
                    for (int y = bbox.Top; y <= bbox.Bottom; y++)
                    {
                        for (int x = bbox.Left; x <= bbox.Right; x++)
                        {
                            float normalX = (x - centerX) / radiusX;
                            float normalY = (y - centerY) / radiusY;
                            if (Math.Abs(normalX) + Math.Abs(normalY) <= 1f)
                            {
                                res.SetTrueReverse(x + strideFactor);
                            }
                        }

                        strideFactor += stride;
                    }
                }
                break;
            }

            case MaskType.Ellipse:
            {
                if (info.Mode == MaskMode.FullImage)
                {
                    sprLeft   = 0;
                    sprTop    = 0;
                    sprRight  = spr.Width - 1;
                    sprBottom = spr.Height - 1;
                }
                else
                {
                    sprLeft   = (int)info.Left;
                    sprTop    = (int)info.Top;
                    sprRight  = (int)info.Right;
                    sprBottom = (int)info.Bottom;
                }

                float centerX = (sprLeft + sprRight) / 2;
                float centerY = (sprTop + sprBottom) / 2;
                float radiusX = centerX - sprLeft + 0.5f;
                float radiusY = centerY - sprTop + 0.5f;

                if (radiusX <= 0 || radiusY <= 0)
                {
                    break;
                }

                if (maskbbox != null)
                {
                    for (int y = bbox.Top; y <= bbox.Bottom; y++)
                    {
                        for (int x = bbox.Left; x <= bbox.Right; x++)
                        {
                            float normalX = (x - centerX) / radiusX;
                            float normalY = (y - centerY) / radiusY;
                            if (Math.Pow(normalX, 2.0d) + Math.Pow(normalY, 2.0d) <= 1.0d)
                            {
                                res.SetTrueReverse(x + strideFactor);
                                if (x < maskbbox.Left)
                                {
                                    maskbbox.Left = x;
                                }
                                if (y < maskbbox.Top)
                                {
                                    maskbbox.Top = y;
                                }
                                if (x > maskbbox.Right)
                                {
                                    maskbbox.Right = x;
                                }
                                if (y > maskbbox.Bottom)
                                {
                                    maskbbox.Bottom = y;
                                }
                            }
                        }

                        strideFactor += stride;
                    }
                }
                else
                {
                    for (int y = bbox.Top; y <= bbox.Bottom; y++)
                    {
                        for (int x = bbox.Left; x <= bbox.Right; x++)
                        {
                            float normalX = (x - centerX) / radiusX;
                            float normalY = (y - centerY) / radiusY;
                            if (Math.Pow(normalX, 2.0d) + Math.Pow(normalY, 2.0d) <= 1.0d)
                            {
                                res.SetTrueReverse(x + strideFactor);
                            }
                        }

                        strideFactor += stride;
                    }
                }
                break;
            }
            }

            return(res);
        }