Currently i'm trying to produce a simple 2D map generation program, and it is pretty much finished apart from one key thing; The movement of the generated islands. The way the program functions it keeps all the islands in the middle of the map separated by colour like in some like disco ball of puke thing, but my main problem is trying to move the islands into new locations.
The program should randomly place the islands in new places based on colour, but i am having a considerable amount of difficulty doing this, as all solutions i have attempted have either fell on their face in a tsunami of 'index out of bounds of the array' errors or have worked, but taken literal hours to move a single island.
TLDR; Do any algorithms exist that would potentially allow me to move shapes made of pixels to random locations while keeping their existing shapes? mine suck.
Edit: I will try and rewrite this to be easier to read later since i'm in a rush, but in essence it reads all the pixels from the circle using .getpixel and stores them in an array based on their colour, it then generates a random location and runs the same code again, only this time it will accept a colour as an argument and will place the colour at the pixel relative to the centre of the circle if it finds a colour that is the same as the colour it is currently accepting.
In theory this should go through every colour and generate a new position for each one that maintains the shape of the island upon generation, but in practice it just takes forever.
//Thoughts - use the circle generator as a radar to find all the seperate colors, then for each color randomly generate an x and a y. then use the circle generator but only apply the colors that are selected
if (tempi >= 716 || tempib > 0)
{
if(tempib <= 0)
{
tempi = 0;
tempib = 1;
randxb = Rander.Next(10, xlen - 10);
randyb = Rander.Next(10, ylen - 10);
}
tempi += 1;
day += 1;
if(day >= 31)
{
month += 1;
day = 1;
}
if(month >= 13)
{
year += 1;
month = 1;
}
AD = "PF";
era = "Prehistoric era";
age = "Islandic Age";
Point temppb = new Point(randxb, randyb);
if (colours[tempib] == Color.DarkBlue || colours[tempib] == Color.FromArgb(0, 0, 0))
{
tempib += 1;
}
else
{
Radar(0, temppb, "write", colours[tempib]);
}
tempi = 0;
tempib += 1;
randxb = Rander.Next(10, xlen - 10);
randyb = Rander.Next(10, ylen - 10);
if (tempib >= islandnuma)
{
age = "Neanderthalic Age";
}
}
else
{
year += Rander.Next(1, 3);
day = 1;
AD = "PF";
era = "Prehistoric era";
Point tempp = new Point(xlen / 2 - 150, ylen / 2 - 150);
tempi += 1;
Radar(tempi, tempp, "scan", Color.White);
if(tempi >= 716)
{
clearmap();
}
}
}
This is the terrible algorithm it calls
Color[,] scanresults = new Color[717, 4499]; //shell, place in shell
private void Radar(int circle, Point pos, string mode, Color col) //Fuck this doesnt work i need to change it
{
using (var g = Graphics.FromImage(pictureBox1.Image))
{
if (mode == "scan")
{
int mj = 0;
if (circle <= 716)
{
for (double i = 0.0; i < 360.0; i += 0.1)
{
mj += 1;
int radius = circle / 2; //max size = 716
double angle = i * System.Math.PI / 180;
int x = pos.X - (int)(radius * System.Math.Cos(angle));
int y = pos.Y - (int)(radius * System.Math.Sin(angle));
Color m = Map.GetPixel(x, y);
scanresults[circle, mj] = Map.GetPixel(x, y);
}
}
else
{
return;
}
}
else
{
if(mode == "write")
{
for(int c2 = 0; c2 <= 716; c2++)
{
int bmj = 0;
for (double i = 0.0; i < 360.0; i += 0.1)
{
try
{
if (mode == "write")
{
bmj += 1;
int radius = (716 - c2) / 2; //max size = 716
double angle = i * System.Math.PI / 180;
int x = pos.X - (int)(radius * System.Math.Cos(angle));
int y = pos.Y - (int)(radius * System.Math.Sin(angle));
if (scanresults[c2, bmj] == col)
{
Map.SetPixel(x, y, col);
}
}
}
catch (Exception em)
{
Console.Write("error: " + em);
}
//Color m = Map.GetPixel(x, y);
//scanresults[circle, mj] = Map.GetPixel(x, y);
}
}
}
}
//Dont hate me im defensive about my terrible coding style
}
}
Related
I'm trying to use MS Chart with custom controls. My purpose is to:
Highlight only a segment of the line that connects two neighboring points on mouse hover over that piece
Find indexes of those two neighboring points (I need that for being able to drag that line by moving two points simultaneously)
Kind of illustration:
For now I can detect a hover over a line on the chart by using the approach described here. But I'm stuck in finding indexes or at least coordinates of those two points.
So the original idea from this question was to find nearest points by x (assuming that all the series has x-values are indeed steadily increasing) and then calculate y-value. But I have a little improved that and added support for completely vertical lines. So here is my code for capturing the needed line:
private static GrippedLine? LineHitTest(Series series, double xPos, double yPos, Axis xAxis, Axis yAxis)
{
double xPixelPos = xAxis.PixelPositionToValue(xPos);
double yPixelPos = yAxis.PixelPositionToValue(yPos);
DataPoint[] neighbors = new DataPoint[2];
neighbors[0] = series.Points.Last(x => x.XValue <= xPixelPos);
neighbors[1] = series.Points.First(x => x.XValue >= xPixelPos);
DataPoint[] verticalMates;
foreach (DataPoint neighbor in neighbors)
{
if (Math.Abs(neighbor.XValue - xPixelPos) < LINE_GRIP_REGION)
{
verticalMates = series.Points.FindAllByValue(neighbor.XValue, "X").ToArray();
if (verticalMates.Length > 1)
{
if (verticalMates.Length > 2)
{
if (verticalMates[0].YValues[0] < verticalMates[verticalMates.Length - 1].YValues[0])
{
neighbors[0] = verticalMates.LastOrDefault(y => y.YValues[0] < yPixelPos);
neighbors[1] = verticalMates.FirstOrDefault(y => y.YValues[0] >= yPixelPos);
}
else
{
neighbors[0] = verticalMates.LastOrDefault(y => y.YValues[0] > yPixelPos);
neighbors[1] = verticalMates.FirstOrDefault(y => y.YValues[0] <= yPixelPos);
}
}
else
{
neighbors[0] = verticalMates[0];
neighbors[1] = verticalMates[1];
}
break;
}
}
}
double x0 = xAxis.ValueToPixelPosition(neighbors[0].XValue);
double y0 = yAxis.ValueToPixelPosition(neighbors[0].YValues[0]);
double x1 = xAxis.ValueToPixelPosition(neighbors[1].XValue);
double y1 = yAxis.ValueToPixelPosition(neighbors[1].YValues[0]);
double Yinterpolated = y0 + (y1 - y0) * (xPos - x0) / (x1 - x0);
int[] linePoints = new int[2];
// if mouse Y position is near the calculated OR the line is vertical
if (Math.Abs(Yinterpolated - yPos) < LINE_GRIP_REGION || neighbors[0].XValue == neighbors[1].XValue)
{
linePoints[0] = series.Points.IndexOf(neighbors[0]);
linePoints[1] = series.Points.IndexOf(neighbors[1]);
}
else
{
return null;
}
return new GrippedLine()
{
startLinePointIndex = linePoints[0],
endLinePointIndex = linePoints[1],
x0Correction = neighbors[0].XValue - xPixelPos,
y0Correction = neighbors[0].YValues[0] - yPixelPos,
x1Correction = neighbors[1].XValue - xPixelPos,
y1Correction = neighbors[1].YValues[0] - yPixelPos
};
}
I have a picturebox with a picture as a background (infact a map), and on it I am spawning rectangles. These rectangles are supposed to move by given points. The rectangle is moving with assigned speed, and after reaching (or getting close) to one of the points, it starts moving to the next one. My problem however is, the rectangle doesnt move directly to the given point, it is just proceeding to get close to only one of the coordinates, so there are situations where the Y coordinate of the rectangle, and the Y coordinate of the point are the same, but the rectangle is like 60 pixels of and wont move.
Below I am adding a picture as an example of the movement. Blue is expected route, red is the actual one. I have checked the coordinates like a hundred times, they are correct, the rectangle is just moving elsewhere. Note: this only happens at some of the points.
Here is the code I am using to count the movement of the rectangle relative to axis X and Y.
public void Move_calculate(Graphics g)
{
if (points[passed].X == 0 || points[passed].Y == 0) // this happens when the rectangle reaches it final point - it stays where it is (working fine)
{
Redraw(g);
return;
}
speed = randomNumbers.Next(7, 13);
if (points[passed].X > x_coordinate && points[passed].Y > y_coordinate)
{
Bx = points[passed].X;
By = points[passed].Y;
distanceForAlfaX = Bx - x_coordinate; // x_coordinate is the x coordinate of the rectangle
distanceForAlfaY = By - y_coordinate;
if (distanceForAlfaX <= 20 || distanceForAlfaY <= 20) speed = 5; // slowing down when approaching the point
if (distanceForAlfaX + distanceForAlfaY <= 15) passed += 1;
alpha = (distanceForAlfaY / distanceForAlfaX); // tangent alpha
x_change = (int)(speed * (Math.Cos(alpha))); // get distance moved relative to axis X
y_change = (int)Math.Sqrt(((speed * speed) + (x_change * x_change))); // again distance for axis Y, using Pythagoras theorem
x_coordinate += x_change;
y_coordinate += y_change;
}
else if (points[passed].X > x_coordinate && points[passed].Y < y_coordinate)
{
Bx = points[passed].X;
By = points[passed].Y;
distanceForAlfaX = Bx - x_coordinate;
distanceForAlfaY = y_coordinate - By;
if (distanceForAlfaX <= 20 || distanceForAlfaY <= 20) speed = 5;
if (distanceForAlfaX + distanceForAlfaY <= 15) passed += 1;
alpha = (distanceForAlfaY / distanceForAlfaX);
x_change = (int)(speed * (Math.Cos(alpha)));
y_change = (int)Math.Sqrt(((speed * speed) + (x_change * x_change)));
x_coordinate += x_change;
y_coordinate -= y_change;
}
else if (points[passed].X < x_coordinate && points[passed].Y > y_coordinate)
{
Bx = points[passed].X;
By = points[passed].Y;
distanceForAlfaX = x_coordinate - Bx;
distanceForAlfaY = By - y_coordinate;
if (distanceForAlfaX <= 20 || distanceForAlfaY <= 20) speed = 5;
if (distanceForAlfaX+distanceForAlfaY <= 15) passed += 1;
alpha = (distanceForAlfaY / distanceForAlfaX);
x_change = (int)(speed * (Math.Cos(alpha)));
y_change = (int)Math.Sqrt(((speed * speed) + (x_change * x_change)));
x_coordinate -= x_change;
y_coordinate += y_change;
}
else if (points[passed].X < x_coordinate && points[passed].Y < y_coordinate)
{
Bx = points[passed].X;
By = points[passed].Y;
distanceForAlfaX = x_coordinate - Bx;
distanceForAlfaY = y_coordinate - By;
if (distanceForAlfaX <= 20 || distanceForAlfaY <= 20) speed = 5;
if (distanceForAlfaX + distanceForAlfaY <= 15) passed += 1;
alpha = (distanceForAlfaY / distanceForAlfaX);
x_change = (int)(speed * (Math.Cos(alpha)));
y_change = (int)Math.Sqrt(((speed * speed) + (x_change * x_change)));
x_coordinate -= x_change;
y_coordinate -= y_change;
}
else
{
MessageBox.Show("Something went wrong"); // just notify me that it isnt working again..
}
Pen p = new Pen(Color.Turquoise, 2);
r = new Rectangle(x_coordinate, y_coordinate, 5, 5); // redraw the rectangle
g.DrawRectangle(p, r);
p.Dispose();
}
I have no idea why this is happening, could anyone help with this?
P.S.
There is absolutely no need for the movement to be smooth, the positions of rectangles will be updated once per two seconds using a Timer. For now it is temporarily set to a button.
EDIT:
Here is the foreach code. The labels are just the coordinates shown next to the PictureBox
foreach (aircraft acft in aircrafts) // aircraft is an array aircrafts[]
{
label2.Text = "xp" + acft.points[acft.passed].X;
label3.Text = "yp" + acft.points[acft.passed].Y;
label4.Text = acft.passed.ToString();
label5.Text = "y" + acft.y_coordinate.ToString();
//MessageBox.Show(acft.points[0].X.ToString());
acft.Move_calculate(e.Graphics);
spawn = string.Empty;
}
EDIT2: All variables in aircraft class
public string callsign;
public int speed;
public double heading;
public bool moving = false;
public Point[] points;
public double alpha;
public int x_change;
public int y_change;
public int x_coordinate;
public int y_coordinate;
public int Bx;
public int By;
public double distanceForAlfaX;
public double distanceForAlfaY;
public int passed = 0;
public Rectangle r;
I guess, there's a math mistake in
y_change = (int)Math.Sqrt(((speed * speed) + (x_change * x_change)));
Moreover...
// again distance for axis Y, using Pythagoras theorem
Let Mr.Pythagoras be, I would rather use the same as for X axis
y_change = (int)(speed * (Math.Sin(alpha)));
Have you checked your coordinate system? (https://web.archive.org/web/20140710074441/http://bobpowell.net/coordinatesystems.aspx)
Sorry, wrong link for your issue. Try debugging using Control.PointToClient to make sure all coordinates are expressed in client space. (https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.pointtoclient)
You could try:
Point cPoint = this.PointToClient(new Point(x_coordinate, y_coordinate));
Size cSize = new Size(5,5);
r = new Rectangle(cPoint, cSize); // redraw the rectangle
g.DrawRectangle(p, r);
Could you post the data types of your variables to clarify your code? You might be losing precision somewhere, especially if you're using integer division.
(Note for DJ KRAZE, a -= b; in C# can also mean a = a - b; Context matters.)
Does anyone know of any code to render an Ellipse to an array in C#? I had a look about, I couldn't find anything that answered my problem.
Given the following array:
bool[,] pixels = new bool[100, 100];
I'm looking for functions to render both a hollow and filled ellipse within a rectangular area. e.g:
public void Ellipse(bool[,] pixels, Rectangle area)
{
// fill pixels[x,y] = true here for the ellipse within area.
}
public void FillEllipse(bool[,] pixels, Rectangle area)
{
// fill pixels[x,y] = true here for the ellipse within area.
}
Ellipse(pixels, new Rectangle(20, 20, 60, 60));
FillEllipse(pixels, new Rectangle(40, 40, 20, 20));
Any help would be greatly appreciated.
Something like this should do the trick
public class EllipseDrawer
{
private static PointF GetEllipsePointFromX(float x, float a, float b)
{
//(x/a)^2 + (y/b)^2 = 1
//(y/b)^2 = 1 - (x/a)^2
//y/b = -sqrt(1 - (x/a)^2) --Neg root for upper portion of the plane
//y = b*-sqrt(1 - (x/a)^2)
return new PointF(x, b * -(float)Math.Sqrt(1 - (x * x / a / a)));
}
public static void Ellipse(bool[,] pixels, Rectangle area)
{
DrawEllipse(pixels, area, false);
}
public static void FillEllipse(bool[,] pixels, Rectangle area)
{
DrawEllipse(pixels, area, true);
}
private static void DrawEllipse(bool[,] pixels, Rectangle area, bool fill)
{
// Get the size of the matrix
var matrixWidth = pixels.GetLength(0);
var matrixHeight = pixels.GetLength(1);
var offsetY = area.Top;
var offsetX = area.Left;
// Figure out how big the ellipse is
var ellipseWidth = (float)area.Width;
var ellipseHeight = (float)area.Height;
// Figure out the radiuses of the ellipses
var radiusX = ellipseWidth / 2;
var radiusY = ellipseHeight / 2;
//Keep track of the previous y position
var prevY = 0;
var firstRun = true;
// Loop through the points in the matrix
for (var x = 0; x <= radiusX; ++x)
{
var xPos = x + offsetX;
var rxPos = (int)ellipseWidth - x - 1 + offsetX;
if (xPos < 0 || rxPos < xPos || xPos >= matrixWidth)
{
continue;
}
var pointOnEllipseBoundCorrespondingToXMatrixPosition = GetEllipsePointFromX(x - radiusX, radiusX, radiusY);
var y = (int) Math.Floor(pointOnEllipseBoundCorrespondingToXMatrixPosition.Y + (int)radiusY);
var yPos = y + offsetY;
var ryPos = (int)ellipseHeight - y - 1 + offsetY;
if (yPos >= 0)
{
if (xPos > -1 && xPos < matrixWidth && yPos > -1 && yPos < matrixHeight)
{
pixels[xPos, yPos] = true;
}
if(xPos > -1 && xPos < matrixWidth && ryPos > -1 && ryPos < matrixHeight)
{
pixels[xPos, ryPos] = true;
}
if (rxPos > -1 && rxPos < matrixWidth)
{
if (yPos > -1 && yPos < matrixHeight)
{
pixels[rxPos, yPos] = true;
}
if (ryPos > -1 && ryPos < matrixHeight)
{
pixels[rxPos, ryPos] = true;
}
}
}
//While there's a >1 jump in y, fill in the gap (assumes that this is not the first time we've tracked y, x != 0)
for (var j = prevY - 1; !firstRun && j > y - 1 && y > 0; --j)
{
var jPos = j + offsetY;
var rjPos = (int)ellipseHeight - j - 1 + offsetY;
if(jPos == rjPos - 1)
{
continue;
}
if(jPos > -1 && jPos < matrixHeight)
{
pixels[xPos, jPos] = true;
}
if(rjPos > -1 && rjPos < matrixHeight)
{
pixels[xPos, rjPos] = true;
}
if (rxPos > -1 && rxPos < matrixWidth)
{
if(jPos > -1 && jPos < matrixHeight)
{
pixels[rxPos, jPos] = true;
}
if(rjPos > -1 && rjPos < matrixHeight)
{
pixels[rxPos, rjPos] = true;
}
}
}
firstRun = false;
prevY = y;
var countTarget = radiusY - y;
for (var count = 0; fill && count < countTarget; ++count)
{
++yPos;
--ryPos;
// Set all four points in the matrix we just learned about
// also, make the indication that for the rest of this row, we need to fill the body of the ellipse
if(yPos > -1 && yPos < matrixHeight)
{
pixels[xPos, yPos] = true;
}
if(ryPos > -1 && ryPos < matrixHeight)
{
pixels[xPos, ryPos] = true;
}
if (rxPos > -1 && rxPos < matrixWidth)
{
if(yPos > -1 && yPos < matrixHeight)
{
pixels[rxPos, yPos] = true;
}
if(ryPos > -1 && ryPos < matrixHeight)
{
pixels[rxPos, ryPos] = true;
}
}
}
}
}
}
Although there already seems to be a perfectly valid answer with source code and all to this question, I just want to point out that the WriteableBitmapEx project also contains a lot of efficient source code for drawing and filling different polygon types (such as ellipses) in so-called WriteableBitmap objects.
This code can easily be adapted to the general scenario where a 2D-array (or 1D representation of a 2D-array) should be rendered in different ways.
For the ellipse case, pay special attention to the DrawEllipse... methods in the WriteableBitmapShapeExtensions.cs file and FillEllipse... methods in the WriteableBitmapFillExtensions.cs file, everything located in the trunk/Source/WriteableBitmapEx sub-folder.
This more applies to all languages in general, and I'm not sure why you're looking for things like this in particular rather than using a pre-existing graphics library (homework?), but for drawing an ellipse, I would suggest using the midpoint line drawing algorithm which can be adapted to an ellipse (also to a circle):
http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
I'm not sure I fully agree that it's a generalisation of Bresenham's algorithm (certainly we were taught that Bresenham's and the Midpoint algorithm are different but proved to produce identical results), but that page should give you a start on it. See the link to the paper near the bottom for an algorithm specific to ellipses.
As for filling the ellipse, I'd say your best bet is to take a scanline approach - look at each row in turn, work out which pixels the lines on the left and right are at, and then fill every pixel inbetween.
The simplest thing to do would do is iterate over each element of your matrix, and check whether some ellipse equation evaluates to true
taken from http://en.wikipedia.org/wiki/Ellipse
What I would start with is something resembling
bool[,] pixels = new bool[100, 100];
double a = 30;
double b = 20;
for (int i = 0; i < 100; i++)
for (int j = 0; j < 100; j++ )
{
double x = i-50;
double y = j-50;
pixels[i, j] = (x / a) * (x / a) + (y / b) * (y / b) > 1;
}
and if your elipse is in reverse, than just change the > to <
For a hollow one, you can check whether the difference between (x / a) * (x / a) + (y / b) * (y / b) and 1 is within a certain threshold. If you just change the inequality to an equation, it will probably miss some pixels.
Now, I haven't actually tested this fully, so I don't know if the equation being applied correctly, but I just want to illustrate the concept.
I'm attempting to add semi-realistic water into my tile-based, 2D platformer. The water must act somewhat lifelike, with a pressure model that runs entirely local. (IE. Can only use data from cells near it) This model is needed because of the nature of my game, where you cannot be certain that the data you need isn't inside an area that isn't in memory.
I've tried one method so far, but I could not refine it enough to work with my constraints.
For that model, each cell would be slightly compressible, depending on the amount of water in the above cell. When a cell's water content was larger than the normal capacity, the cell would try to expand upwards. This created a fairly nice simulation, abeit slow (Not lag; Changes in the water were taking a while to propagate.), at times. When I tried to implement this into my engine, I found that my limitations lacked the precision required for it to work. I can provide a more indepth explanation or a link to the original concept if you wish.
My constraints:
Only 256 discrete values for water level. (No floating point variables :( ) -- EDIT. Floats are fine.
Fixed grid size.
2D Only.
U-Bend Configurations must work.
The language that I'm using is C#, but I can probably take other languages and translate it to C#.
The question is, can anyone give me a pressure model for water, following my constraints as closely as possible?
How about a different approach?
Forget about floats, that's asking for roundoff problems in the long run. Instead, how about a unit of water?
Each cell contains a certain number of units of water. Each iteration you compare the cell with it's 4 neighbors and move say 10% (change this to alter the propagation speed) of the difference in the number of units of water. A mapping function translates the units of water into a water level.
To avoid calculation order problems use two values, one for the old units, one for the new. Calculate everything and then copy the updated values back. 2 ints = 8 bytes per cell. If you have a million cells that's still only 8mb.
If you are actually trying to simulate waves you'll need to also store the flow--4 values, 16 mb. To make a wave put some inertia to the flow--after you calculate the desired flow then move the previous flow say 10% of the way towards the desired value.
Try treating each contiguous area of water as a single area (like flood fill) and track 1) the lowest cell(s) where water can escape and 2) the highest cell(s) from which water can come, then move water from the top to the bottom. This isn't local, but I think you can treat the edges of the area you want to affect as not connected and process any subset that you want. Re-evaluate what areas are contiguous on each frame (re-flood on each frame) so that when blobs converge, they can start being treated as one.
Here's my code from a Windows Forms demo of the idea. It may need some fine tuning, but seems to work quite well in my tests:
public partial class Form1 : Form
{
byte[,] tiles;
const int rows = 50;
const int cols = 50;
public Form1()
{
SetStyle(ControlStyles.ResizeRedraw, true);
InitializeComponent();
tiles = new byte[cols, rows];
for (int i = 0; i < 10; i++)
{
tiles[20, i+20] = 1;
tiles[23, i+20] = 1;
tiles[32, i+20] = 1;
tiles[35, i+20] = 1;
tiles[i + 23, 30] = 1;
tiles[i + 23, 32] = 1;
tiles[21, i + 15] = 2;
tiles[21, i + 4] = 2;
if (i % 2 == 0) tiles[22, i] = 2;
}
tiles[20, 30] = 1;
tiles[20, 31] = 1;
tiles[20, 32] = 1;
tiles[21, 32] = 1;
tiles[22, 32] = 1;
tiles[33, 32] = 1;
tiles[34, 32] = 1;
tiles[35, 32] = 1;
tiles[35, 31] = 1;
tiles[35, 30] = 1;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (SolidBrush b = new SolidBrush(Color.White))
{
for (int y = 0; y < rows; y++)
{
for (int x = 0; x < cols; x++)
{
switch (tiles[x, y])
{
case 0:
b.Color = Color.White;
break;
case 1:
b.Color = Color.Black;
break;
default:
b.Color = Color.Blue;
break;
}
e.Graphics.FillRectangle(b, x * ClientSize.Width / cols, y * ClientSize.Height / rows,
ClientSize.Width / cols + 1, ClientSize.Height / rows + 1);
}
}
}
}
private bool IsLiquid(int x, int y)
{
return tiles[x, y] > 1;
}
private bool IsSolid(int x, int y)
{
return tiles[x, y] == 1;
}
private bool IsEmpty(int x, int y)
{
return IsEmpty(tiles, x, y);
}
public static bool IsEmpty(byte[,] tiles, int x, int y)
{
return tiles[x, y] == 0;
}
private void ProcessTiles()
{
byte processedValue = 0xFF;
byte unprocessedValue = 0xFF;
for (int y = 0; y < rows; y ++)
for (int x = 0; x < cols; x++)
{
if (IsLiquid(x, y))
{
if (processedValue == 0xff)
{
unprocessedValue = tiles[x, y];
processedValue = (byte)(5 - tiles[x, y]);
}
if (tiles[x, y] == unprocessedValue)
{
BlobInfo blob = GetWaterAt(new Point(x, y), unprocessedValue, processedValue, new Rectangle(0, 0, 50, 50));
blob.ProcessMovement(tiles);
}
}
}
}
class BlobInfo
{
private int minY;
private int maxEscapeY;
private List<int> TopXes = new List<int>();
private List<int> BottomEscapeXes = new List<int>();
public BlobInfo(int x, int y)
{
minY = y;
maxEscapeY = -1;
TopXes.Add(x);
}
public void NoteEscapePoint(int x, int y)
{
if (maxEscapeY < 0)
{
maxEscapeY = y;
BottomEscapeXes.Clear();
}
else if (y < maxEscapeY)
return;
else if (y > maxEscapeY)
{
maxEscapeY = y;
BottomEscapeXes.Clear();
}
BottomEscapeXes.Add(x);
}
public void NoteLiquidPoint(int x, int y)
{
if (y < minY)
{
minY = y;
TopXes.Clear();
}
else if (y > minY)
return;
TopXes.Add(x);
}
public void ProcessMovement(byte[,] tiles)
{
int min = TopXes.Count < BottomEscapeXes.Count ? TopXes.Count : BottomEscapeXes.Count;
for (int i = 0; i < min; i++)
{
if (IsEmpty(tiles, BottomEscapeXes[i], maxEscapeY) && (maxEscapeY > minY))
{
tiles[BottomEscapeXes[i], maxEscapeY] = tiles[TopXes[i], minY];
tiles[TopXes[i], minY] = 0;
}
}
}
}
private BlobInfo GetWaterAt(Point start, byte unprocessedValue, byte processedValue, Rectangle bounds)
{
Stack<Point> toFill = new Stack<Point>();
BlobInfo result = new BlobInfo(start.X, start.Y);
toFill.Push(start);
do
{
Point cur = toFill.Pop();
while ((cur.X > bounds.X) && (tiles[cur.X - 1, cur.Y] == unprocessedValue))
cur.X--;
if ((cur.X > bounds.X) && IsEmpty(cur.X - 1, cur.Y))
result.NoteEscapePoint(cur.X - 1, cur.Y);
bool pushedAbove = false;
bool pushedBelow = false;
for (; ((cur.X < bounds.X + bounds.Width) && tiles[cur.X, cur.Y] == unprocessedValue); cur.X++)
{
result.NoteLiquidPoint(cur.X, cur.Y);
tiles[cur.X, cur.Y] = processedValue;
if (cur.Y > bounds.Y)
{
if (IsEmpty(cur.X, cur.Y - 1))
{
result.NoteEscapePoint(cur.X, cur.Y - 1);
}
if ((tiles[cur.X, cur.Y - 1] == unprocessedValue) && !pushedAbove)
{
pushedAbove = true;
toFill.Push(new Point(cur.X, cur.Y - 1));
}
if (tiles[cur.X, cur.Y - 1] != unprocessedValue)
pushedAbove = false;
}
if (cur.Y < bounds.Y + bounds.Height - 1)
{
if (IsEmpty(cur.X, cur.Y + 1))
{
result.NoteEscapePoint(cur.X, cur.Y + 1);
}
if ((tiles[cur.X, cur.Y + 1] == unprocessedValue) && !pushedBelow)
{
pushedBelow = true;
toFill.Push(new Point(cur.X, cur.Y + 1));
}
if (tiles[cur.X, cur.Y + 1] != unprocessedValue)
pushedBelow = false;
}
}
if ((cur.X < bounds.X + bounds.Width) && (IsEmpty(cur.X, cur.Y)))
{
result.NoteEscapePoint(cur.X, cur.Y);
}
} while (toFill.Count > 0);
return result;
}
private void timer1_Tick(object sender, EventArgs e)
{
ProcessTiles();
Invalidate();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
int x = e.X * cols / ClientSize.Width;
int y = e.Y * rows / ClientSize.Height;
if ((x >= 0) && (x < cols) && (y >= 0) && (y < rows))
tiles[x, y] = 2;
}
}
}
From a fluid dynamics viewpoint, a reasonably popular lattice-based algorithm family is the so-called Lattice Boltzmann method. A simple implementation, ignoring all the fine detail that makes academics happy, should be relatively simple and fast and also get reasonably correct dynamics.
In another thread on XNA, Callum Rogers wrote some code which creates a texture with the outline of a circle, but I'm trying to create a circle filled with a color. What I have to modify on this code to fill the circle with color?
public Texture2D CreateCircle(int radius)
{
int outerRadius = radius*2 + 2; // So circle doesn't go out of bounds
Texture2D texture = new Texture2D(GraphicsDevice, outerRadius, outerRadius);
Color[] data = new Color[outerRadius * outerRadius];
// Colour the entire texture transparent first.
for (int i = 0; i < data.Length; i++)
data[i] = Color.Transparent;
// Work out the minimum step necessary using trigonometry + sine approximation.
double angleStep = 1f/radius;
for (double angle = 0; angle < Math.PI*2; angle += angleStep)
{
// Use the parametric definition of a circle: http://en.wikipedia.org/wiki/Circle#Cartesian_coordinates
int x = (int)Math.Round(radius + radius * Math.Cos(angle));
int y = (int)Math.Round(radius + radius * Math.Sin(angle));
data[y * outerRadius + x + 1] = Color.White;
}
texture.SetData(data);
return texture;
}
Don't use a texture for stuff like this (especially for things being in one single color!) - also don't try to do it pixel by pixel. You've got 3D acceleration for a reason.
Just draw the circle similar to a pie using a triangle fan. You'll need the following vertices.
Center of the circle
x points on the circle's border.
The first two points will define a line between the center of the circle and its border. The third vertex will define the first polygon. Vertices 1, 3 and 4 will then define the second polygon, etc.
To get the points on the circle's border use the formulas from your example. The first angle will be 0°, the following ones multiples of (360° / points on circle). To get a full circle you'll need one additional point that matches the second point (the first point on the border).
Depending on the number of vertices on the circle you'll get different n-gons. The more vertices you use the rounder the shape will look (at some performance cost):
(Less than 2 vertices aren't possible as a polygon requires at least 3 vertices to be drawn.)
Total of 4 points (3 points on circle) will result in a triangle.
Total of 5 points (4 point on circle) will result in a square.
Total of 6 points (5 points on circle) will result in a pentagon
...
Actually the XNA example for drawing primites show how to draw a circle (or n-gon) using a triangle fan.
well for anyone who wants to do it pixel by pixel ... i made a solution based on the information given. In your 2d texture method add the following code to fill the circle. I'm making a game and wanted to be able to make circles different colors and sizes. So inside CreateCircle(int radius) method, add the following code after the outline has been created :
bool finished = false;
int firstSkip = 0;
int lastSkip = 0;
for (int i = 0; i <= data.Length - 1; i++)
{
if (finished == false)
{
//T = transparent W = White;
//Find the First Batch of Colors TTTTWWWTTTT The top of the circle
if ((data[i] == Color.White) && (firstSkip == 0))
{
while (data[i + 1] == Color.White)
{
i++;
}
firstSkip = 1;
i++;
}
//Now Start Filling TTTTTTTTWWTTTTTTTT
//circle in Between TTTTTTW--->WTTTTTT
//transaparent blancks TTTTTWW--->WWTTTTT
// TTTTTTW--->WTTTTTT
// TTTTTTTTWWTTTTTTTT
if (firstSkip == 1)
{
if (data[i] == Color.White && data[i + 1] != Color.White)
{
i++;
while (data[i] != Color.White)
{
//Loop to check if its the last row of pixels
//We need to check this because of the
//int outerRadius = radius * 2 + -->'2'<--;
for (int j = 1; j <= outerRadius; j++)
{
if (data[i + j] != Color.White)
{
lastSkip++;
}
}
//If its the last line of pixels, end drawing
if (lastSkip == outerRadius)
{
break;
finished = true;
}
else
{
data[i] = Color.White;
i++;
lastSkip = 0;
}
}
while (data[i] == Color.White)
{
i++;
}
i--;
}
}
}
}
// Set the data when finished
//-- don't need to paste this part, already given up above
texture.SetData(data);
return texture;
If you need to do it from scratch (though I'm guessing there are easier ways), change the way you perform the rendering. Instead of iterating through angles and plotting pixels, iterate through pixels and determine where they are relative to the circle. If they are <R, draw as fill color. If they are ~= R, draw as border color.
I know that I'm a little late, but I modified your code to fill in the center
public static Texture2D CreateCircle(GraphicsDevice importedGraphicsDevice, int radius)
{
int outerRadius = radius * 2 + 2; // So circle doesn't go out of bounds
Texture2D texture = new Texture2D(importedGraphicsDevice, outerRadius, outerRadius);
Color[] data = new Color[outerRadius * outerRadius];
// Colour the entire texture transparent first.
for (int i = 0; i < data.Length; i++)
data[i] = Color.Transparent;
// Work out the minimum step necessary using trigonometry + sine approximation.
double angleStep = 1f / radius;
for (double angle = 0; angle < Math.PI * 2; angle += angleStep)
{
// Use the parametric definition of a circle: http://en.wikipedia.org/wiki/Circle#Cartesian_coordinates
int x = (int)Math.Round(radius + radius * Math.Cos(angle));
int y = (int)Math.Round(radius + radius * Math.Sin(angle));
data[y * outerRadius + x + 1] = Color.White;
}
//width
for (int i = 0; i < outerRadius; i++)
{
int yStart = -1;
int yEnd = -1;
//loop through height to find start and end to fill
for (int j = 0; j < outerRadius; j++)
{
if (yStart == -1)
{
if (j == outerRadius - 1)
{
//last row so there is no row below to compare to
break;
}
//start is indicated by Color followed by Transparent
if (data[i + (j * outerRadius)] == Color.White && data[i + ((j + 1) * outerRadius)] == Color.Transparent)
{
yStart = j + 1;
continue;
}
}
else if (data[i + (j * outerRadius)] == Color.White)
{
yEnd = j;
break;
}
}
//if we found a valid start and end position
if (yStart != -1 && yEnd != -1)
{
//height
for (int j = yStart; j < yEnd; j++)
{
data[i + (j * outerRadius)] = new Color(10, 10, 10, 10);
}
}
}
texture.SetData(data);
return texture;
}