I am using clipper for performing a bunch of operations on polygons which represent the outlines of objects in my program. The thing is, I now want to clip a grid of line segments to fill these outlines but I am struggling to do this with Clipper.
At the moment I am converting my lines to rectangles with a thickness of 2 units which I then do an intersection operation with and then finally I need to revert the new polygons back to line segments. This is quite inefficient though and produces quite a few errors.
Can this be done with Clipper, if not is there something else that I can use or do I need to implement my own line segment clipper?
Using Clipper ver 6 ...
using ClipperLib;
using Path = List<IntPoint>;
using Paths = List<List<IntPoint>>;
static Polygon IntsToPolygon(Int64[] ints)
{
int len1 = ints.Length / 2;
Polygon result = new Polygon(len1);
for (int i = 0; i < len1; i++)
result.Add(new IntPoint(ints[i * 2], ints[i * 2 + 1]));
return result;
}
static void Main(string[] args)
{
Paths clip = new Paths(); //clipping polygon(s)
//make a polygon in the shape of a pentagram
//with center (200,200) and radius 100 ...
Int64[][] ints1 = new Int64[][]
{
new Int64[]{222, 169, 295, 169, 236, 212, 259, 281, 200, 238,
141, 281, 164, 212, 105, 169, 178, 169, 200, 100}
};
clip.Add(IntsToPolygon(ints1[0]));
Paths subj = new Paths(); //subject paths, could be open or closed here
int gridWidth = 5;
int loopCnt = 200 / gridWidth;
//now make grid lines and add them to subj ready for clipping ...
//add horizontal grid lines ...
for (int i = 0; i <= loopCnt; i++)
{
Path gridLine = new Path(2);
gridLine.Add(new IntPoint(100, 100 + i * gridWidth));
gridLine.Add(new IntPoint(300, 100 + i * gridWidth));
subj.Add(gridLine);
}
//add vertical grid lines ...
for (int i = 0; i <= loopCnt; i++)
{
Path gridLine = new Path(2);
gridLine.Add(new IntPoint(100 + i * gridWidth, 100));
gridLine.Add(new IntPoint(100 + i * gridWidth, 300));
subj.Add(gridLine);
}
//now clip the gridlines (subj) with the pentagram (clip) ...
Clipper c = new Clipper();
PolyTree solution = new PolyTree();
c.AddPaths(subj, PolyType.ptSubject, false); //ie paths NOT closed
c.AddPaths(clip, PolyType.ptClip, true); //nb: clip paths MUST be closed
c.Execute(ClipType.ctIntersection,
solution, PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd);
Paths clippedGrids = Clipper.PolyTreeToPaths(solution);
//Display the result (sorry, make your own display function) ...
//Display(Paths p, Color stroke, Color brush, PolyFillType pft, bool IsClosed)
Display(subj, Color.FromArgb(0x18, 0, 0, 0x9c),
Color.Blue, PolyFillType.NonZero, false); //unclipped grid
Display(clippedGrids, FromArgb(0, 0, 0, 0),
Color.Red, false); //clipped grid
}
Related
I have an ellipse which is growing with time.
To detect the ellipse I have used CvInvoke.AbsDiff method .
and I gets an image like this
I want to put this ellipse to fit-ellipse method and gain the radius es of it.
This is the approach I took.
CvInvoke.AbsDiff(First, img, grayscale);
CvInvoke.CvtColor(grayscale, grayscale, ColorConversion.Bgr2Gray);
CvInvoke.GaussianBlur(grayscale, grayscale, new System.Drawing.Size(11, 11), 15, 15);
CvInvoke.Threshold(grayscale, grayscale, Convert.ToInt16(Threshold), Convert.ToInt16(Threshold * 2), ThresholdType.Binary );
Mat element = CvInvoke.GetStructuringElement(Emgu.CV.CvEnum.ElementShape.Rectangle, new System.Drawing.Size(3, 3), new System.Drawing.Point(-1, -1));
CvInvoke.Dilate(grayscale, grayscale, element, new System.Drawing.Point(-1, 1), 5, BorderType.Constant, new MCvScalar(255, 255, 255));
CvInvoke.Canny(grayscale, grayscale, Threshold, MaxThreshold * 2, 3);
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
CvInvoke.FindContours(grayscale, contours, null, RetrType.Ccomp, ChainApproxMethod.ChainApproxTc89Kcos);
double area = 0;
double ContourArea = 0;
int contour = 0;
int CenterX;
int CenterY;
for (int i = 0; i < contours.Size; i++)
{
System.Drawing.Rectangle rec = CvInvoke.BoundingRectangle(contours[i]);
output.Draw(rec, new Bgr(255, 0, 255), 2);
CenterX = ((rec.Width) / 2) + rec.X;
CenterY = ((rec.Height) / 2) + rec.Y;
ContourArea = rec.Width * rec.Height; ;
if ((HWidth - CenterFactor) < CenterX && CenterX < (HWidth + CenterFactor) && (HHeight - CenterFactor) < CenterY && CenterY< (HHeight + CenterFactor) )
{
if (ContourArea < 1000000)
if (area < ContourArea)
{
area = ContourArea;
contour = i;
}
}
}
//if (contour == 0)
//{
// return arr;
//}
System.Drawing.Rectangle rect = CvInvoke.BoundingRectangle(contours[contour]);
output.Draw(rect, new Bgr(0, 255, 0), 3);
But i am not getting the best ellipse everytime. This is the contour which I'm getting
Is there any other way to do this?
Although this method is not completely perfect, this could be a possible direction that you could take.
Mat input = CvInvoke.Imread(#"C:\Users\ajones\Desktop\Images\inputImg.png", ImreadModes.AnyColor);
Mat input2 = input.Clone();
Mat thresh = new Mat();
CvInvoke.GaussianBlur(input, thresh, new System.Drawing.Size(7, 7), 10, 10);
CvInvoke.Threshold(thresh, thresh, 3, 10, ThresholdType.Binary);
CvInvoke.Imshow("The Thresh", thresh);
CvInvoke.WaitKey(0);
Mat output = new Mat();
CvInvoke.CvtColor(thresh, output, ColorConversion.Bgr2Gray);
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
CvInvoke.FindContours(output, contours, null, RetrType.External, ChainApproxMethod.ChainApproxSimple);
CvInvoke.DrawContours(input, contours, -1, new MCvScalar(0, 255, 0), 3, LineType.FourConnected);
CvInvoke.Imshow("The Image", input);
CvInvoke.WaitKey(0);
int biggest = 0;
int index = 0;
for (int i = 0; i<contours.Size; i++)
{
if (contours[i].Size > biggest)
{
biggest = contours[i].Size;
index = i;
}
}
CvInvoke.DrawContours(input2, contours, index, new MCvScalar(0, 255, 0), 3, LineType.FourConnected);
CvInvoke.Imshow("The Image2", input2);
CvInvoke.WaitKey(0);
First blur the image using a Gaussian filter.
Then, using a binary threshold.
Afterwards, find all contours on the image
Finally, all you would need to do is just sort through your contours until you found the biggest one.
Like I said, its not completely perfect, but I should help push you in the right direction.
I'm generating a barcode depending on how many inputs that the user set in the numericUpDown control. The problem is when generating a lot of barcodes, the other barcodes cannot be seen in the printpreviewdialog because it I cannot apply a nextline or \n every 4-5 Images.
int x = 0, y = 10;
for (int i = 1; i <= int.Parse(txtCount.Text); i++)
{
idcount++;
connection.Close();
Zen.Barcode.Code128BarcodeDraw barcode = Zen.Barcode.BarcodeDrawFactory.Code128WithChecksum;
Random random = new Random();
string randomtext = "MLQ-";
int j;
for (j = 1; j <= 6; j++)
{
randomtext += random.Next(0, 9).ToString();
Image barcodeimg = barcode.Draw(randomtext, 50);
resultimage = new Bitmap(barcodeimg.Width, barcodeimg.Height + 20);
using (var graphics = Graphics.FromImage(resultimage))
using (var font = new Font("Arial", 11)) // Any font you want
using (var brush = new SolidBrush(Color.Black))
using (var format = new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Far}) // Also, horizontally centered text, as in your example of the expected output
{
graphics.Clear(Color.White);
graphics.DrawImage(barcodeimg, 0, 0);
graphics.DrawString(randomtext, font, brush, resultimage.Width / 2, resultimage.Height, format);
}
x += 25;
}
e.Graphics.DrawImage(resultimage, x, y);
}
There's no "new lines" in rasterized graphics. There's pixels. You've got the right idea, every n number of images, add a new line. But since you're working with pixels, let's say every 4 images you're going to need to add a vertical offset by modifying the y coordinate of all your graphics draw calls. This offset, combined with a row height in pixels could look something like this:
var rowHeight = 250; // pixels
var maxColumns = 4;
var verticalOffset = (i % maxColums) * rowHeight;
Then, when you can supply a y coordinate, starting at or near 0, add the vertical offset to it.
I'm trying to draw rectangles into a WriteableBitmap, unfortunatelly the WriteableBitmapEx that provides Fill* extensions are too slow and can be run only at the main thread.
I'm looking for alternatives specific for WP8.1 and don't know the best solution so far.
I need a way to draw the rectangles async, one approach was creating a Canvas at the MainWindow and adding xaml.Rectangles on it, this almost can be used as solution for the problem, but I want specific draw the rectangles on the WriteableBitmap instead of creating a ton of UIElements and adding all of then on the screen.
Sorry if any given solution can be found on internet, I can't find almost nothing about C#.
A test I did:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var container = new Canvas()
{
Width = 300,
Height = 500
};
var winImage = new Image()
{
Width = 300,
Height = 500
};
container.Children.Add(winImage);
//var winImage = imageView.NativeView<Image>();
var img = new WriteableBitmap((int)winImage.Width, (int)winImage.Height);
var clr = Color.FromArgb(255, 0, 0, 255);
var start = DateTime.Now;
var random = new Random();
for (int i = 0; i < 50; i++)
{
//var color = Color.FromArgb(255, (byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255));
img.FillRectangle(i * 2, i * 2, i * 2 + 10, i * 2 + 10, clr);
}
Debug.WriteLine((DateTime.Now - start).TotalMilliseconds + "ms drawing");
winImage.Source = img;
Content = container;
}
This results in "792.1397ms drawing" running on debug mode on a Nokia Lumia 1020, that is pretty slow.
Using GetBitmapContext() should make it a lot faster.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var container = new Canvas()
{
Width = 300,
Height = 500
};
var winImage = new Image()
{
Width = 300,
Height = 500
};
container.Children.Add(winImage);
var img = BitmapFactory.New((int)winImage.Width, (int)winImage.Height);
winImage.Source = img;
Content = container;
var clr = Color.FromArgb(255, 0, 0, 255);
var random = new Random();
var sw = new Stopwatch();
sw.Start();
using (img.GetBitmapContext()) {
img.Clear(Colors.White);
for (var x = 0; x < 10; x++) {
for (var y = 0; y < 10; y++) {
img.FillRectangle(x * 10, y * 10, x * 10 + 10, y * 10 + 10, clr);
}
}
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds + "ms drawing");
}
I have managed to make a surface from a set point points in vtk. Now I need to cut a plane through the surface and make a 2D contour that I can output as vtkImageData. I have made code that only makes a projection onto a plane. Can anyone tell me what I am doing wrong to get a cut plane through the polydata?
vtkSphereSource sphereSource = vtkSphereSource.New();
sphereSource.SetPhiResolution(30);
sphereSource.SetThetaResolution(30);
sphereSource.SetCenter(40, 40, 0);
sphereSource.SetRadius(20);
vtkDataSetSurfaceFilter surfaceFilter = vtkDataSetSurfaceFilter.New();
surfaceFilter.SetInputConnection(sphereSource.GetOutputPort());
surfaceFilter.Update();
// generate circle by cutting the sphere with an implicit plane
// (through its center, axis-aligned)
vtkCutter circleCutter = vtkCutter.New();
circleCutter.SetInputConnection(sphereSource.GetOutputPort());
vtkPlane cutPlane = vtkPlane.New();
double[] Origin = sphereSource.GetCenter();
cutPlane.SetOrigin(Origin[0], Origin[1], Origin[2]);
cutPlane.SetNormal(0, 0, 1);
circleCutter.SetCutFunction(cutPlane);
vtkStripper stripper = vtkStripper.New();
stripper.SetInputConnection(circleCutter.GetOutputPort()); // valid circle
stripper.Update();
// that's our circle
vtkPolyData circle = stripper.GetOutput();
// prepare the binary image's voxel grid
vtkImageData whiteImage = vtkImageData.New();
double[] bounds;
bounds = circle.GetBounds();
whiteImage.SetNumberOfScalarComponents(1);
// whiteImage.SetScalarTypeToChar();
whiteImage.SetScalarType(3);
whiteImage.SetSpacing(.5, .5, .5);
// compute dimensions
int[] dim = new int[3];
for (int i = 0; i < 3; i++)
{
dim[i] = (int)Math.Ceiling((bounds[i * 2 + 1] - bounds[i * 2]) / .5) + 1;
if (dim[i] < 1)
dim[i] = 1;
}
whiteImage.SetDimensions(dim[0], dim[1], dim[2]);
whiteImage.SetExtent(0, dim[0] - 1, 0, dim[1] - 1, 0, dim[2] - 1);
whiteImage.SetOrigin(bounds[0], bounds[2], bounds[4]);
whiteImage.AllocateScalars();
// fill the image with foreground voxels:
byte inval = 255;
byte outval = 0;
int count = whiteImage.GetNumberOfPoints();
for (int i = 0; i < count; ++i)
{
whiteImage.GetPointData().GetScalars().SetTuple1(i, inval);
}
// sweep polygonal data (this is the important thing with contours!)
vtkLinearExtrusionFilter extruder = vtkLinearExtrusionFilter.New();
extruder.SetInput(circle); ///todo warning this maybe setinputconnection
extruder.SetScaleFactor(1.0);
extruder.SetExtrusionTypeToNormalExtrusion();
extruder.SetVector(0, 0, 1);
extruder.Update();
// polygonal data -. image stencil:
vtkPolyDataToImageStencil pol2stenc = vtkPolyDataToImageStencil.New();
pol2stenc.SetTolerance(0); // important if extruder.SetVector(0, 0, 1) !!!
pol2stenc.SetInputConnection(extruder.GetOutputPort());
pol2stenc.SetOutputOrigin(bounds[0], bounds[2], bounds[4]);
pol2stenc.SetOutputSpacing(.5, .5, .5);
int[] Extent = whiteImage.GetExtent();
pol2stenc.SetOutputWholeExtent(Extent[0], Extent[1], Extent[2], Extent[3], Extent[4], Extent[5]);
pol2stenc.Update();
// cut the corresponding white image and set the background:
vtkImageStencil imgstenc = vtkImageStencil.New();
imgstenc.SetInput(whiteImage);
imgstenc.SetStencil(pol2stenc.GetOutput());
imgstenc.ReverseStencilOff();
imgstenc.SetBackgroundValue(outval);
imgstenc.Update();
vtkImageData stencil = imgstenc.GetOutput();
int[] Dims = stencil.GetDimensions();
int[,] DataMap = new int[Dims[0], Dims[1]];
I think you are getting the contour correctly: http://www.vtk.org/Wiki/VTK/Examples/Cxx/Filtering/ContoursFromPolyData - now how do you expect to output a contour as an image? You just want to set "pixels on the contour" to some value, and "pixels not on the contour" to a different value? How about this? http://www.vtk.org/Wiki/VTK/Examples/Cxx/PolyData/PolyDataContourToImageData
I'm trying to find a method of storing references to roughly 70 million texture2d/vector2 sets. To clarify a bit, I need to be able to use 100 or so texture2d's and then assign them a vector2 XY value for a tile map that would have around 70 million tiles. I just need a refernce of which texture2d goes with which vector2. I will be dynamically/procedurally generating the tile/co-ordinate sets I just need a method of storing all of them now that won't blow up my ram. I tried to use a dictionary with the vector2 as the key and the texture2d as the value but that killed over on me with an OutOfMemoryException. So I then tried Wintellect Power Collections and used their MultiDictionary. But even if i assigned a texture2d as the only key and used the vector2 as the 70 million values to that key it still killed over with the same exception.
So I'm at a loss as to how I should proceed now. I just need to store the references for later access. I'm not trying to draw them to the screen or anything like that so I can't figure out why it takes 700mb of ram just for the dictionary. As an after thought I just realized that I've been letting these dictionaries resize themselves. Could that be the issue ?
I hope this was specific enough I've been up all night so I won't bother cleaning up and posting any code right now. If you think my code is the culprit and not my methods though, let me know and I'll post it for you. Looking forward to your answers.
EDIT: Ok here's the code I'm working with right now. I cleaned it up a bit but I know its not the greatest looking code. If you see any obvious problems or even inefficiencies I'd be happy to hear about them. Just don't bash me to badly since this is the first time I've worked with c# :)
The 70 million tiles come into play when WorldWidth & WorldHeight are set to 8400 each.
class MapMaker
{
SpriteSheet spriteSheetMap;
SpriteSheet spriteSheet1;
SpriteSheet spriteSheet2;
SpriteSheet spriteSheet3;
SpriteSheet spriteSheet4;
SpriteSheet spriteSheet5;
SpriteSheet spriteSheet6;
SpriteSheet spriteSheet7;
SpriteSheet spriteSheet8;
SpriteSheet spriteSheet9;
SpriteSheet spriteSheet10;
SpriteSheet spriteSheet11;
SpriteSheet spriteSheet12;
SpriteSheet spriteSheet13;
SpriteSheet spriteSheet14;
SpriteSheet spriteSheet15;
SpriteSheet spriteSheet16;
SpriteSheet spriteSheet17;
SpriteSheet spriteSheet18;
SpriteSheet spriteSheet19;
SpriteSheet spriteSheet20;
SpriteSheet spriteSheet21;
SpriteSheet spriteSheet22;
SpriteSheet spriteSheet23;
Random rnd = new Random();
int WorldWidth = 250;
int WorldHeight = 250;
List<int> sprites = new List<int>();
int[][] TheGrid = new int[10][];
int posX = 0, posY = 0, gridX = 0, gridY = 0;
Dictionary<int, Texture2D> TileStorage = new Dictionary<int, Texture2D>();
Dictionary<Vector2, Texture2D> SineSaver = new Dictionary<Vector2, Texture2D>();
public void loadTiles(ContentManager Content)
{
spriteSheetMap = new SpriteSheet();
spriteSheet1 = new SpriteSheet();
spriteSheet2 = new SpriteSheet();
spriteSheet3 = new SpriteSheet();
spriteSheet4 = new SpriteSheet();
spriteSheet5 = new SpriteSheet();
spriteSheet6 = new SpriteSheet();
spriteSheet7 = new SpriteSheet();
spriteSheet8 = new SpriteSheet();
spriteSheet9 = new SpriteSheet();
spriteSheet10 = new SpriteSheet();
spriteSheet11 = new SpriteSheet();
spriteSheet12 = new SpriteSheet();
spriteSheet13 = new SpriteSheet();
spriteSheet14 = new SpriteSheet();
spriteSheet15 = new SpriteSheet();
spriteSheet16 = new SpriteSheet();
spriteSheet17 = new SpriteSheet();
spriteSheet18 = new SpriteSheet();
spriteSheet19 = new SpriteSheet();
spriteSheet20 = new SpriteSheet();
spriteSheet21 = new SpriteSheet();
spriteSheet22 = new SpriteSheet();
spriteSheet23 = new SpriteSheet();
spriteSheetMap.Map = Content.Load<Dictionary<string, Rectangle>>("Tiles/Map");
TileStorage.Add(0, spriteSheet1.Sheet = Content.Load<Texture2D>("test2"));
TileStorage.Add(1, spriteSheet1.Sheet = Content.Load<Texture2D>("Tiles/Amethyst"));
TileStorage.Add(2, spriteSheet2.Sheet = Content.Load<Texture2D>("Tiles/Amethyst_N"));
TileStorage.Add(3, spriteSheet3.Sheet = Content.Load<Texture2D>("Tiles/Aquamarine"));
TileStorage.Add(4, spriteSheet4.Sheet = Content.Load<Texture2D>("Tiles/Aquamarine_N"));
TileStorage.Add(5, spriteSheet5.Sheet = Content.Load<Texture2D>("Tiles/Citrine"));
TileStorage.Add(6, spriteSheet6.Sheet = Content.Load<Texture2D>("Tiles/Citrine_N"));
TileStorage.Add(7, spriteSheet7.Sheet = Content.Load<Texture2D>("Tiles/Diamond"));
TileStorage.Add(8, spriteSheet8.Sheet = Content.Load<Texture2D>("Tiles/Diamond_N"));
TileStorage.Add(9, spriteSheet9.Sheet = Content.Load<Texture2D>("Tiles/Dirt1"));
TileStorage.Add(10, spriteSheet10.Sheet = Content.Load<Texture2D>("Tiles/Dirt2"));
TileStorage.Add(11, spriteSheet11.Sheet = Content.Load<Texture2D>("Tiles/Emerald"));
TileStorage.Add(12, spriteSheet12.Sheet = Content.Load<Texture2D>("Tiles/Emerald_N"));
TileStorage.Add(13, spriteSheet13.Sheet = Content.Load<Texture2D>("Tiles/Peridot"));
TileStorage.Add(14, spriteSheet14.Sheet = Content.Load<Texture2D>("Tiles/Peridot_N"));
TileStorage.Add(15, spriteSheet15.Sheet = Content.Load<Texture2D>("Tiles/Ruby"));
TileStorage.Add(16, spriteSheet16.Sheet = Content.Load<Texture2D>("Tiles/Ruby_N"));
TileStorage.Add(17, spriteSheet17.Sheet = Content.Load<Texture2D>("Tiles/Sand"));
TileStorage.Add(18, spriteSheet18.Sheet = Content.Load<Texture2D>("Tiles/Sapphire"));
TileStorage.Add(19, spriteSheet19.Sheet = Content.Load<Texture2D>("Tiles/Stone1"));
TileStorage.Add(20, spriteSheet20.Sheet = Content.Load<Texture2D>("Tiles/Stone2"));
TileStorage.Add(21, spriteSheet21.Sheet = Content.Load<Texture2D>("Tiles/Stone3"));
TileStorage.Add(22, spriteSheet22.Sheet = Content.Load<Texture2D>("Tiles/Topaz"));
TileStorage.Add(23, spriteSheet23.Sheet = Content.Load<Texture2D>("Tiles/Topaz_N"));
CreateMapKey();
}
private void CreateMapKey()
{
TheGrid[0] = new int[] { 0, 3, 14, 25, 36, 47, 58, 69, 80, 91 };
TheGrid[1] = new int[] { 12, 4, 15, 26, 37, 48, 59, 70, 81, 92 };
TheGrid[2] = new int[] { 23, 5, 16, 27, 38, 49, 60, 71, 82, 93 };
TheGrid[3] = new int[] { 34, 6, 17, 28, 39, 50, 61, 72, 83, 94 };
TheGrid[4] = new int[] { 45, 7, 18, 29, 40, 51, 62, 73, 84, 95 };
TheGrid[5] = new int[] { 56, 8, 19, 30, 41, 52, 63, 74, 85, 96 };
TheGrid[6] = new int[] { 67, 9, 20, 31, 42, 53, 64, 75, 86, 97 };
TheGrid[7] = new int[] { 78, 10, 21, 32, 43, 54, 65, 76, 87, 98 };
TheGrid[8] = new int[] { 89, 11, 22, 33, 44, 55, 66, 77, 88, 99 };
TheGrid[9] = new int[] { 1, 13, 24, 35, 46, 57, 68, 79, 90, 2 };
BaseTileset();
}
private void BaseTileset()
{
int hillLocation = 300, hillWidth = 120, hillHeight = 10;
for (int i = 0; i < WorldHeight * WorldWidth; i++)
{
if (i % WorldHeight * 5 == 0)
{
hillLocation += rnd.Next(-40, 40);
hillWidth += rnd.Next(-10, 10);
if (hillWidth == 0) { hillWidth = 1; }
hillHeight += rnd.Next(-5, 5);
}
Vector2 position = new Vector2(posX, posY);
Texture2D tile = TileStorage[9];
double sine = hillLocation + Math.Sin(posX / hillWidth) * hillHeight;
double cosine = hillLocation + Math.Cos(posX / hillWidth) * hillHeight / 2;
if (posY <= sine || posY < cosine)
{
tile = null;
}
if (tile != null)
{
SineSaver.Add(position, tile);
sprites.Add(TheGrid[gridY][gridX]);
}
posY += 20;
if (posY > (WorldHeight - 1) * 20) { posY = 0; posX += 20; }
gridX = posX / 20 % 10;
gridY = posY / 20 % 10;
}
}
public void DrawLevel(SpriteBatch spriteBatch, GraphicsDeviceManager graphics)
{
spriteBatch.Begin();
int i = 0;
foreach (KeyValuePair<Vector2, Texture2D> entry in SineSaver)
{
spriteBatch.Draw(entry.Value, entry.Key, spriteSheetMap[sprites[i]], Color.White);
i++;
}
spriteBatch.End();
}
}
I'd recommend looking into the flyweight pattern (http://en.wikipedia.org/wiki/Flyweight_pattern)
If you know the width and height of each tile, you can calculate the Vector2 position of each tile - it's deterministic based on an index so it doesn't need to be stored. The only data that's needed for each tile is one int for the index, and a 'type' identifier which could be as small as a single byte.
70 million * (4 + 1 )bytes = 333.786011 megabytes
EDIT 1: Elaborating slightly..
Lets say we have a 3x3 grid of tiles - we know there are 9 tiles in total, so we assign each tile an index 0-8..
Tile[] tiles = new Tile[9]
for (int i = 0; i < 9; i++)
tiles[i].Index = i;
Knowing that each grid row is 3 tiles across, and each column 3 tiles down, we can use the modulo and division operators to get the row & column for any tile index...
Tile tile = getSomeTile();
int column = tile.Index % 3; // = column 1
int row = tile.Index \ 3; // = row 1 - tile 4 is in the middle :)
knowing the width and height of each tile (lets say 10 pixels) we can now calculate the exact position of tile 4:
Vector2 position = new Vector2
{
X = 10f * column,
Y = 10f * row
};
EDIT 2: In response to comment...
Your Tile object would need to contain a type identifier like so:
struct Tile
{
int Index; // The tiles index in the map [0 - 70 million].
byte TileTypeId; // An identifier for a tile type.
}
class TileType // This is the flyweight object..
{
Texture2D Texture; // Gets the texture reference for the tile type...
// any other information about the tile ie. is it collidable? is it water? etc..
}
Then when it comes to drawing tiles...
Tile tile = tiles[someIndex];
TileType type = tileTypes[tile.TileTypeId]; // tileTypes could be a dictionary...
Vector2 position = this.CalculateTilePosition(tile.Index); // Calculate the position as above...
spriteBatch.Draw(type.Texture, position);
EDIT 3: In response to serialization comment...
Serializing the tile grid should be quite easy, it's just a looong sequence of bytes. Assuming the tiles are stored in order, we already know the tiles index number, so that doesn't need to be stored in the file.
Back to the 3x3 grid example:
Example binary data file:
0xF4 0x15 0x5A 0xB5 0x00 0x61 0xEE 0xA3 0x39
BinaryReader reader = OpenSomeBinaryFile();
for (int i = 0; i < (3 * 3); i++)
tiles[i] = new Tile { Index = i, TileTypeId= reader.ReadByte() };
// easy to optimize by reading bigger binary chunks (say an Int64) and bit shifting to get the individual bytes...
// If you happen to have reasonbly large series of tiles with the same type id (byte), the file will be quite well suited to compression on disk, which is a bonus :)
In order to avoid the enourmous memory issues (70 million tiles even at 1 byte per tile is 70Mb) you may want to consider some sort of streaming implementation.
At its most basic something like a WorldChunk that contains NxN tiles, and a link to another WorldChunk (either a direct link, or just an ID or String to identify it). Each of these chunks could have an OnEnterFrame and OnExitFrame method.
In the OnEnterFrame method you could invoke a load from file on all linked chunks that would populate them with data from your game files, and OnExit you would destroy them. In this sort of case you would only keep a small set of chunks around, a subset of your 8400x8400 world. 3x3 or 5x5 are good choices. Ideally these chunks would fill the screen or slightly more than the screen.
Pseudocode concept follows:
class WorldChunk
{
String MyName;
String[8] LinkedWorldChunkFileNames;
Tile[32,32] Tiles;
void OnEnterFrame()
{
LoadWorldChunkFromName(MyName);
foreach(name in LinkedWorldChunkFileNames)
{
LoadWorldChunkFromName(name);
}
}
void LoadWorldChunkFromName(string name)
{
string fileData = LoadFromFile(name); //this should probably be done earlier,
//when a neighbor loads it should load
// offscreen nieghbors
//files then parse them on its own enter
//frame
Tiles = ParseToTiles(fileData); //your own file parsing here
}
void OnExitFrame()
{
Tiles.Clear();
}
}
I have not implemented anything like this yet, but it is something i am starting to give thought too since i need to do something very similar in 3d.
Why don't you just make a class that extends Vector2, but takes an extra variable in the constructor that says which Texture2D belongs to it? This way everything would be stored in the objects itself, which is quite efficient. So you probably don't need any other data structure!
Edit: Or even just make an array of all possible textures, and just give an index to your custom object. This way you don't have to copy the whole Texture2D object every time!
Edit2: As (as I should have known and has been pointed out to me) you cannot extend to a Vector2, I would do something like this:
public class TextureVector2D
{
public Vector2D vector;
public int textureIndex;
public TextureVector2D( Vector2D v, int tI )
{
vector = v;
textureIndex = tI;
}
}
So I dunno if your spritesheets have multiple tiles within them or whatnot... but you could do something like:
class GameTile
{
private Vector2D _vec;
//These integers should be sized to fit your needs
//int8 = up to 256 tilesets
//int16 = up to 65536 tiles in a set
private int8 _tileSet;
private int16 _tileIndex;
//This is the tiles position in the world
public Vector2D Position { get { return _vec; } }
//This is the index of the tileset in the array of all tilesets
public int8 CurrentTileset { get { return _tileSet; } }
//This is the flattened index (row * width + column)
//of the tile in the tileset
public int16 CurrentTile { get { return _tileIndex; } }
}