Drawing a tiled map with Monogame - c#

I've been trying to implement a function that lets me draw the tiles inside a tiled file (.tmx). I've looked around and found a kind of working piece of code but it stretches out the tiles.
This is the code I found and edited a bit:
private void DrawLayer(int index, SpriteBatch batch) {
for (var i = 0; i < Map.Layers[index].Tiles.Count; i++) {
//Get the identification of the tile
int gid = Map.Layers[index].Tiles[i].Gid;
// Empty tile, do nothing
if (gid == 0) { }
else {
int tileFrame = gid - 1 ;
int column = tileFrame % (tileset.Width / tileWidth);
int row = tileFrame / (tileset.Height / tileHeight);
float x = (i % Map.Width) * Map.TileWidth;
float y = (float)Math.Floor(i / (double)Map.Width) * Map.TileHeight;
//Put all the data together in a new rectangle
Rectangle tilesetRec = new Rectangle(tileWidth * column, tileHeight * row, tileWidth, tileHeight);
//Draw the tile that is within the tilesetRec
batch.Draw(tileset, new Rectangle((int)x, (int)y, tileWidth, tileHeight), tilesetRec, Color.White);
}
}
}

The MonoGame.Extended library has support for loading and rendering Tiled (.tmx) maps. It's open source so you can check out how it works if you want.
The layer rendering code supports different map types (orthogonal, isometric), different rendering order (right down, right up, left down, left up) and multiple tilesets so it's not boiled down into a single method like yours.
If you where to extract the relevant bits of code you might end up with something like this:
for (var y = 0; y < layerHeight; y++)
{
for (var x = 0; x < layerWidth; x++)
{
var region = tile.Id == 0 ? null : _regions[tile.Id];
if (region != null)
{
var tx = tile.X * _map.TileWidth;
var ty = tile.Y * _map.TileHeight;
var sourceRectangle = region.Bounds;
var destinationRectangle = new Rectangle(tx, ty, region.Width, region.Height);
_spriteBatch.Draw(region.Texture, destinationRectangle, sourceRectangle, Color.White);
}
}
}
Of course, there's still a few missing bits, like the dictionary of texture regions created when loading the tileset.
_regions = new Dictionary<int, TextureRegion2D>();
for (var y = Margin; y < texture.Height - Margin; y += TileHeight + Spacing)
{
for (var x = Margin; x < texture.Width - Margin; x += TileWidth + Spacing)
{
_regions.Add(id, new TextureRegion2D(Texture, x, y, TileWidth, TileHeight));
id++;
}
}
And the definition of what a texture region actually is.
public class TextureRegion2D
{
public Texture2D Texture { get; protected set; }
public int X { get; private set; }
public int Y { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
public Rectangle Bounds { get { return new Rectangle(X, Y, Width, Height); } }
}
Keep in mind that I've mostly copy and pasted code out of MonoGame.Extended. It won't work exactly as it's written here but I think I've provided enough detail to figure out what all the other variables do if you want to write your own Tiled rendering code.

Related

C# creating LinkedPixels from a Bitmap in an Optimized way

I am doing image manipulation, I have a class that I've inherited that I can't change because it's referenced too many times to completely refactor. Here is the class as it stands.
I have a Bitmap and I am trying to get a collection of all of the LinkedPixels. For example pixel
x, y would be a key and a pixel in the center would be attached to four pixels, (x+1, y), (x, y + 1), (x-1, y), (y-1, x). Pixels on the sides would have three attached Pixels, and Pixels in the corners only two.
imWrap.Arr is an array with all of the Pixels inside of a Bitmap, this is built as the program spools up so this is not built on the fly it is already available.
Since I cannot change the class itself (I could change it to a struct if necessary) I need to find the fastest way to create the LinkedPix, I am aware that there are many better ways to do this but I am locked in to this "LinkedPix" class, there are over 100 references to it
public class LinkedPix
{
public Point P;
public override string ToString()
{
return P.ToString() + " " + Links.Count;
}
public HashSet<LinkedPix> Links;
public SegObject MemberOfObject { get; set; }
public bool IsMemberOfObject { get => MemberOfObject != null; }
private void Init()
{
MemberOfObject = null;
Links = new HashSet<LinkedPix>();
}
public LinkedPix(int x, int y)
{
Init();
P = new Point(x, y);
}
public LinkedPix(Point p)
{
Init();
P = p;
}
public LinkedPix RotateCopy(double theta_radians)
{
//Assumes that 0,0 is the origin
LinkedPix LP2 = new LinkedPix(new Point(
(int)(P.X * Math.Cos(theta_radians) - P.Y * Math.Sin(theta_radians)),
(int)(P.Y * Math.Cos(theta_radians) + P.X * Math.Sin(theta_radians))));
return LP2;
}
public void LinksRemove(LinkedPix ToRemove, bool TwoWay = false)
{
//Remove from each others
if (TwoWay) ToRemove.Links.Remove(this);
Links.Remove(ToRemove);
}
/// <summary>
/// Removes this from each of its links and removes these links
/// </summary>
internal void LinksCrossRemove()
{
foreach (LinkedPix tPix in Links) tPix.LinksRemove(this, false);
Links = new HashSet<LinkedPix>();
}
}
Here are the three functions I used to populate the information about how LinkedPix are related.
Dictionary<Point, LinkedPix> PointDict = new Dictionary<Point, LinkedPix>();
void EstablishLinks(int xi, int yi, LinkedPix Pixi)
{
if (imWrap.Arr[xi, yi] > threshold)
{
Point pi = new Point(xi, yi);
LinkedPix Pix2 = GetOrAdd(pi);
Pixi.Links.Add(Pix2);
Pix2.Links.Add(Pixi);
}
}
LinkedPix Pix; Point p;
int height = imWrap.Height;
for (int y = 0; y < height - 1; y++)
{
if (Slice && y % Slice_ModulusFactor < 1) continue; //This is for Axon Degeneration to break up the axons
for (int x = 0; x < height - 1; x++)
{
if (Slice && x % Slice_ModulusFactor < 1) continue;
if (imWrap.Arr[x, y] > threshold)
{
Pixels++;
if (CreateLinks)
{
p = new Point(x, y);
Pix = GetOrAdd(p);
if (y + 1 < height) EstablishLinks(x, y + 1, Pix);
if (x + 1 < height) EstablishLinks(x + 1, y, Pix);
}
}
}
}
}
public LinkedPix GetOrAdd(Point p)
{
if (PointDict.ContainsKey(p))
{
return PointDict[p];
}
else
{
LinkedPix LP = new LinkedPix(p);
PointDict.Add(p, LP);
return LP;
}
}
}
imWrap.Arr[x, y] is an array that has already been pre-populated with the contents of a bitmap.
things I have tried
Switching to ConcurrentDictionaries and running in Parallel. The inferior Hashing Algorithm of ConcurrentDictionary compared to HashSet means running this in Parallel actually takes more time, at least when I tried it.
switching to a different collection with faster adds such as a Que. This gains me some speed but because of the way LinkedPix is used, I ultimately have to convert to a HashSet anyway which costs time in the end so this is not ultimately a win.
Various types of Asynchronicity, I either gain very little or nothing at all.
I'm definitely stuck and could use some outside of the box thinking. I definitely need to speed this up and any help would be greatly appreciated.
These are some timings
GetOrAdd: 4.190254371881144 ticks
        LinksAdd: 1.7282694093881512 ticks
        LinksAddTwo: 1.6570632131524925 ticks
        PointCreateTime: 0.20380008184398646 ticks
        ArrayCheck: 0.3061780882729328 ticks

From C# serverside, Is there anyway to generate a treemap and save as an image?

I have been using this javascript library to create treemap on webpages and it works great. The issue now is that I need to include this in a powerpoint presentation that I am generating on the server side (I am generating the powerpoint using aspose.slides for .net)
The easiest thing I thought of was to try to somehow build a treemap on the server and save as an image (as adding an image into the powerpoint presentation is quite simple) but after googling, I don't see any solution that from C# serverside can generate a treemap as an image.
Does something like this exist where I can create a treemap as an image from a server side C# app.
Given that algorithms are known, it's not hard to just draw a bitmap with a treemap. At the moment I don't have enough time to write code myself, but I have enough time to (almost) mindlessly port some existing code to C# :) Let's take this javascript implementation. It uses algorithm described in this paper. I found some problems in that implementation, which are fixed in C# version. Javascript version works with pure arrays (and arrays of arrays of arrays) of integers. We define some class instead:
public class TreemapItem {
private TreemapItem() {
FillBrush = Brushes.White;
BorderBrush = Brushes.Black;
TextBrush = Brushes.Black;
}
public TreemapItem(string label, int area, Brush fillBrush) : this() {
Label = label;
Area = area;
FillBrush = fillBrush;
Children = null;
}
public TreemapItem(params TreemapItem[] children) : this() {
// in this implementation if there are children - all other properies are ignored
// but this can be changed in future
Children = children;
}
// Label to write on rectangle
public string Label { get; set; }
// color to fill rectangle with
public Brush FillBrush { get; set; }
// color to fill rectangle border with
public Brush BorderBrush { get; set; }
// color of label
public Brush TextBrush { get; set; }
// area
public int Area { get; set; }
// children
public TreemapItem[] Children { get; set; }
}
Then starting to port. First Container class:
class Container {
public Container(int x, int y, int width, int height) {
X = x;
Y = y;
Width = width;
Height = height;
}
public int X { get; }
public int Y { get; }
public int Width { get; }
public int Height { get; }
public int ShortestEdge => Math.Min(Width, Height);
public IDictionary<TreemapItem, Rectangle> GetCoordinates(TreemapItem[] row) {
// getCoordinates - for a row of boxes which we've placed
// return an array of their cartesian coordinates
var coordinates = new Dictionary<TreemapItem, Rectangle>();
var subx = this.X;
var suby = this.Y;
var areaWidth = row.Select(c => c.Area).Sum()/(float) Height;
var areaHeight = row.Select(c => c.Area).Sum()/(float) Width;
if (Width >= Height) {
for (int i = 0; i < row.Length; i++) {
var rect = new Rectangle(subx, suby, (int) (areaWidth), (int) (row[i].Area/areaWidth));
coordinates.Add(row[i], rect);
suby += (int) (row[i].Area/areaWidth);
}
}
else {
for (int i = 0; i < row.Length; i++) {
var rect = new Rectangle(subx, suby, (int) (row[i].Area/areaHeight), (int) (areaHeight));
coordinates.Add(row[i], rect);
subx += (int) (row[i].Area/areaHeight);
}
}
return coordinates;
}
public Container CutArea(int area) {
// cutArea - once we've placed some boxes into an row we then need to identify the remaining area,
// this function takes the area of the boxes we've placed and calculates the location and
// dimensions of the remaining space and returns a container box defined by the remaining area
if (Width >= Height) {
var areaWidth = area/(float) Height;
var newWidth = Width - areaWidth;
return new Container((int) (X + areaWidth), Y, (int) newWidth, Height);
}
else {
var areaHeight = area/(float) Width;
var newHeight = Height - areaHeight;
return new Container(X, (int) (Y + areaHeight), Width, (int) newHeight);
}
}
}
Then Treemap class which builds actual Bitmap
public class Treemap {
public Bitmap Build(TreemapItem[] items, int width, int height) {
var map = BuildMultidimensional(items, width, height, 0, 0);
var bmp = new Bitmap(width, height);
var g = Graphics.FromImage(bmp);
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
foreach (var kv in map) {
var item = kv.Key;
var rect = kv.Value;
// fill rectangle
g.FillRectangle(item.FillBrush, rect);
// draw border
g.DrawRectangle(new Pen(item.BorderBrush, 1), rect);
if (!String.IsNullOrWhiteSpace(item.Label)) {
// draw text
var format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
var font = new Font("Arial", 16);
g.DrawString(item.Label, font, item.TextBrush, new RectangleF(rect.X, rect.Y, rect.Width, rect.Height), format);
}
}
return bmp;
}
private Dictionary<TreemapItem, Rectangle> BuildMultidimensional(TreemapItem[] items, int width, int height, int x, int y) {
var results = new Dictionary<TreemapItem, Rectangle>();
var mergedData = new TreemapItem[items.Length];
for (int i = 0; i < items.Length; i++) {
// calculate total area of children - current item's area is ignored
mergedData[i] = SumChildren(items[i]);
}
// build a map for this merged items (merged because their area is sum of areas of their children)
var mergedMap = BuildFlat(mergedData, width, height, x, y);
for (int i = 0; i < items.Length; i++) {
var mergedChild = mergedMap[mergedData[i]];
// inspect children of children in the same way
if (items[i].Children != null) {
var headerRect = new Rectangle(mergedChild.X, mergedChild.Y, mergedChild.Width, 20);
results.Add(mergedData[i], headerRect);
// reserve 20 pixels of height for header
foreach (var kv in BuildMultidimensional(items[i].Children, mergedChild.Width, mergedChild.Height - 20, mergedChild.X, mergedChild.Y + 20)) {
results.Add(kv.Key, kv.Value);
}
}
else {
results.Add(mergedData[i], mergedChild);
}
}
return results;
}
private Dictionary<TreemapItem, Rectangle> BuildFlat(TreemapItem[] items, int width, int height, int x, int y) {
// normalize all area values for given width and height
Normalize(items, width*height);
var result = new Dictionary<TreemapItem, Rectangle>();
Squarify(items, new TreemapItem[0], new Container(x, y, width, height), result);
return result;
}
private void Normalize(TreemapItem[] data, int area) {
var sum = data.Select(c => c.Area).Sum();
var multi = area/(float) sum;
foreach (var item in data) {
item.Area = (int) (item.Area*multi);
}
}
private void Squarify(TreemapItem[] data, TreemapItem[] currentRow, Container container, Dictionary<TreemapItem, Rectangle> stack) {
if (data.Length == 0) {
foreach (var kv in container.GetCoordinates(currentRow)) {
stack.Add(kv.Key, kv.Value);
}
return;
}
var length = container.ShortestEdge;
var nextPoint = data[0];
if (ImprovesRatio(currentRow, nextPoint, length)) {
currentRow = currentRow.Concat(new[] {nextPoint}).ToArray();
Squarify(data.Skip(1).ToArray(), currentRow, container, stack);
}
else {
var newContainer = container.CutArea(currentRow.Select(c => c.Area).Sum());
foreach (var kv in container.GetCoordinates(currentRow)) {
stack.Add(kv.Key, kv.Value);
}
Squarify(data, new TreemapItem[0], newContainer, stack);
}
}
private bool ImprovesRatio(TreemapItem[] currentRow, TreemapItem nextNode, int length) {
// if adding nextNode
if (currentRow.Length == 0)
return true;
var newRow = currentRow.Concat(new[] {nextNode}).ToArray();
var currentRatio = CalculateRatio(currentRow, length);
var newRatio = CalculateRatio(newRow, length);
return currentRatio >= newRatio;
}
private int CalculateRatio(TreemapItem[] row, int length) {
var min = row.Select(c => c.Area).Min();
var max = row.Select(c => c.Area).Max();
var sum = row.Select(c => c.Area).Sum();
return (int) Math.Max(Math.Pow(length, 2)*max/Math.Pow(sum, 2), Math.Pow(sum, 2)/(Math.Pow(length, 2)*min));
}
private TreemapItem SumChildren(TreemapItem item) {
int total = 0;
if (item.Children?.Length > 0) {
total += item.Children.Sum(c => c.Area);
foreach (var child in item.Children) {
total += SumChildren(child).Area;
}
}
else {
total = item.Area;
}
return new TreemapItem(item.Label, total, item.FillBrush);
}
}
Now let's try to use and see how it goes:
var map = new[] {
new TreemapItem("ItemA", 0, Brushes.DarkGray) {
Children = new[] {
new TreemapItem("ItemA-1", 200, Brushes.White),
new TreemapItem("ItemA-2", 500, Brushes.BurlyWood),
new TreemapItem("ItemA-3", 600, Brushes.Purple),
}
},
new TreemapItem("ItemB", 1000, Brushes.Yellow) {
},
new TreemapItem("ItemC", 0, Brushes.Red) {
Children = new[] {
new TreemapItem("ItemC-1", 200, Brushes.White),
new TreemapItem("ItemC-2", 500, Brushes.BurlyWood),
new TreemapItem("ItemC-3", 600, Brushes.Purple),
}
},
new TreemapItem("ItemD", 2400, Brushes.Blue) {
},
new TreemapItem("ItemE", 0, Brushes.Cyan) {
Children = new[] {
new TreemapItem("ItemE-1", 200, Brushes.White),
new TreemapItem("ItemE-2", 500, Brushes.BurlyWood),
new TreemapItem("ItemE-3", 600, Brushes.Purple),
}
},
};
using (var bmp = new Treemap().Build(map, 1024, 1024)) {
bmp.Save("output.bmp", ImageFormat.Bmp);
}
Output:
This can be extended in multiple ways, and code quality can certainly be improved significantly. But if you would go this way, it can at least give you a good start. Benefit is that it's fast and no external dependencies involved.
If you would want to use it and find some issues or it does not match some of your requirements - feel free to ask and I will improve it when will have more time.
Using the GDI+ api may be your only choice, with good support cross platform. However, there are several potential issues you need to be aware of when doing anything with GDI+ on the server side. It's worth reading this as it explains the current state of drawing Graphics in DotNet and has a point on Server-side processing:
https://github.com/imazen/Graphics-vNext
Having said that; there's this article that deals with what you're asking for:
OutOfMemory Exception when recursively drawing rectangles in GDI+ (It's specifically talking about generating a TreeMap with GDI+ and if you read the comments and answer you'll avoid a lot of the pitfalls)
Once you've generated your image, it's a trivial process to save it to disk somewhere and then, hopefully, embed it within your presentation; you have options to write to streams as well, so it may be possible to directly embed it in the powerpoint file without first saving it to disk.
You can use WPF rendering: http://lordzoltan.blogspot.co.uk/2010/09/using-wpf-to-render-bitmaps.html but it's not without its drawbacks.
(That's a link to my own old blog - but if you search for 'using wpf to generate images' you'll get lots of other examples - many of which are better than mine!)
Generating a tree in WPF is going to be, well, challenging, though - although it can be done, since the WPF drawing primitives are hierarchical in nature.
It might not be suitable, bou could also consider GraphViz - https://github.com/JamieDixon/GraphViz-C-Sharp-Wrapper however I don't know how much luck you'll have executing the command line in a web server.
There are - I expect - bound to be paid-for libraries to do this, as well, as it's a common need.
The Eindhoven University of Technology has published a paper on the algorithm of squarified treemaps. Pascal Laurin has turned this into C#. There is also a Code Project article that has a section about treemaps.
Of course there are also commercial solutions like the one from .NET Charting, Infragistics or Telerik. The downside of those is probably that they are designed as Controls which need to be painted, so you might need some sort of UI thread.
There is also a question here on Stack Overflow that already asked for treemap implementations in C#. Just in case you don't remember.
Since you are already generating the JS and the HTML version of everything, one thing you might want to check out is :
http://www.nrecosite.com/html_to_image_generator_net.aspx
I use this to generate high res-graphic reports straight from my generated pages. It used WKHTML to render it and you can pass a ton of parameters to it to really fine tune it. Its free to most things and it works great. Multi-threading is kind of a pain in the butt with it but I haven't run into to many issues. If you use the NRECO PDf library you can even do batches of stuff also.
With this all you would have to do is render the page like you already are, pipe it through the library and insert into your PPT and all should be good.
Since all you need to do is to extract a screenshot of the web page, it would be more convenient to capture the web page as an Image.
This free library is capable of extracting screenshot from your web page, and it supports Javascript / CSS.
I'm basing the following solution in this project about treemaps in WPF.
Using the data in your link, you can define your model (only with necesary data) like this:
class Data
{
[JsonProperty("$area")]
public float Area { get; set; }
[JsonProperty("$color")]
public Color Color { get; set; }
}
class Item
{
public string Name { get; set; }
public Data Data { get; set; }
public IEnumerable<Item> Children { get; set; }
internal TreeMapData TMData { get; set; }
internal int GetDepth()
{
return Children.Select(c => c.GetDepth()).DefaultIfEmpty().Max() + 1;
}
}
Adding an extra property TreeMapData with some values used in the solution:
class TreeMapData
{
public float Area { get; set; }
public SizeF Size { get; set; }
public PointF Location { get; set; }
}
Now, defining a TreeMap class with the following public members:
class TreeMap
{
public IEnumerable<Item> Items { get; private set; }
public TreeMap(params Item[] items) :
this(items.AsEnumerable()) { }
public TreeMap(IEnumerable<Item> items)
{
Items = items.OrderByDescending(t => t.Data.Area).ThenByDescending(t => t.Children.Count());
}
public Bitmap Draw(int width, int height)
{
var bmp = new Bitmap(width + 1, height + 1);
using (var g = Graphics.FromImage(bmp))
{
DrawIn(g, 0, 0, width, height);
g.Flush();
}
return bmp;
}
//Private members
}
So, you can use it like this:
var treeMap = new TreeMap(items);
var bmp = treeMap.Draw(1366, 768);
And the private/helper members:
private RectangleF emptyArea;
private void DrawIn(Graphics g, float x, float y, float width, float height)
{
Measure(width, height);
foreach (var item in Items)
{
var sFormat = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
if (item.Children.Count() > 0)
{
g.FillRectangle(Brushes.DimGray, x + item.TMData.Location.X, y + item.TMData.Location.Y, item.TMData.Size.Width, 15);
g.DrawString(item.Name, SystemFonts.DefaultFont, Brushes.LightGray, new RectangleF(x + item.TMData.Location.X, y + item.TMData.Location.Y, item.TMData.Size.Width, 15), sFormat);
var treeMap = new TreeMap(item.Children);
treeMap.DrawIn(g, x + item.TMData.Location.X, y + item.TMData.Location.Y + 15, item.TMData.Size.Width, item.TMData.Size.Height - 15);
}
else
{
g.FillRectangle(new SolidBrush(item.Data.Color), x + item.TMData.Location.X, y + item.TMData.Location.Y, item.TMData.Size.Width, item.TMData.Size.Height);
g.DrawString(item.Name, SystemFonts.DefaultFont, Brushes.Black, new RectangleF(x + item.TMData.Location.X, y + item.TMData.Location.Y, item.TMData.Size.Width, item.TMData.Size.Height), sFormat);
}
var pen = new Pen(Color.Black, item.GetDepth() * 1.5f);
g.DrawRectangle(pen, x + item.TMData.Location.X, y + item.TMData.Location.Y, item.TMData.Size.Width, item.TMData.Size.Height);
}
g.Flush();
}
private void Measure(float width, float height)
{
emptyArea = new RectangleF(0, 0, width, height);
var area = width * height;
var sum = Items.Sum(t => t.Data.Area + 1);
foreach (var item in Items)
{
item.TMData = new TreeMapData();
item.TMData.Area = area * (item.Data.Area + 1) / sum;
}
Squarify(Items, new List<Item>(), ShortestSide());
foreach (var child in Items)
if (!IsValidSize(child.TMData.Size))
child.TMData.Size = new Size(0, 0);
}
private void Squarify(IEnumerable<Item> items, IEnumerable<Item> row, float sideLength)
{
if (items.Count() == 0)
{
ComputeTreeMaps(row);
return;
}
var item = items.First();
List<Item> row2 = new List<Item>(row);
row2.Add(item);
List<Item> items2 = new List<Item>(items);
items2.RemoveAt(0);
float worst1 = Worst(row, sideLength);
float worst2 = Worst(row2, sideLength);
if (row.Count() == 0 || worst1 > worst2)
Squarify(items2, row2, sideLength);
else
{
ComputeTreeMaps(row);
Squarify(items, new List<Item>(), ShortestSide());
}
}
private void ComputeTreeMaps(IEnumerable<Item> items)
{
var orientation = this.GetOrientation();
float areaSum = 0;
foreach (var item in items)
areaSum += item.TMData.Area;
RectangleF currentRow;
if (orientation == RowOrientation.Horizontal)
{
currentRow = new RectangleF(emptyArea.X, emptyArea.Y, areaSum / emptyArea.Height, emptyArea.Height);
emptyArea = new RectangleF(emptyArea.X + currentRow.Width, emptyArea.Y, Math.Max(0, emptyArea.Width - currentRow.Width), emptyArea.Height);
}
else
{
currentRow = new RectangleF(emptyArea.X, emptyArea.Y, emptyArea.Width, areaSum / emptyArea.Width);
emptyArea = new RectangleF(emptyArea.X, emptyArea.Y + currentRow.Height, emptyArea.Width, Math.Max(0, emptyArea.Height - currentRow.Height));
}
float prevX = currentRow.X;
float prevY = currentRow.Y;
foreach (var item in items)
{
var rect = GetRectangle(orientation, item, prevX, prevY, currentRow.Width, currentRow.Height);
item.TMData.Size = rect.Size;
item.TMData.Location = rect.Location;
ComputeNextPosition(orientation, ref prevX, ref prevY, rect.Width, rect.Height);
}
}
private RectangleF GetRectangle(RowOrientation orientation, Item item, float x, float y, float width, float height)
{
if (orientation == RowOrientation.Horizontal)
return new RectangleF(x, y, width, item.TMData.Area / width);
else
return new RectangleF(x, y, item.TMData.Area / height, height);
}
private void ComputeNextPosition(RowOrientation orientation, ref float xPos, ref float yPos, float width, float height)
{
if (orientation == RowOrientation.Horizontal)
yPos += height;
else
xPos += width;
}
private RowOrientation GetOrientation()
{
return emptyArea.Width > emptyArea.Height ? RowOrientation.Horizontal : RowOrientation.Vertical;
}
private float Worst(IEnumerable<Item> row, float sideLength)
{
if (row.Count() == 0) return 0;
float maxArea = 0;
float minArea = float.MaxValue;
float totalArea = 0;
foreach (var item in row)
{
maxArea = Math.Max(maxArea, item.TMData.Area);
minArea = Math.Min(minArea, item.TMData.Area);
totalArea += item.TMData.Area;
}
if (minArea == float.MaxValue) minArea = 0;
float val1 = (sideLength * sideLength * maxArea) / (totalArea * totalArea);
float val2 = (totalArea * totalArea) / (sideLength * sideLength * minArea);
return Math.Max(val1, val2);
}
private float ShortestSide()
{
return Math.Min(emptyArea.Width, emptyArea.Height);
}
private bool IsValidSize(SizeF size)
{
return (!size.IsEmpty && size.Width > 0 && size.Width != float.NaN && size.Height > 0 && size.Height != float.NaN);
}
private enum RowOrientation
{
Horizontal,
Vertical
}
Finally, to parse and draw the json in the example I'm doing this:
var json = File.ReadAllText(#"treemap.json");
var items = JsonConvert.DeserializeObject<Item>(json);
var treeMap = new TreeMap(items);
var bmp = treeMap.Draw(1366, 768);
bmp.Save("treemap.png", ImageFormat.Png);
And the resulting image:
Actually I don't know if the following can help you or not since you aren't using vsto, AND AS SAID IN THE COMMENTS PROBABLY IS A BAD IDEA.
Starting in Office 2016, treemaps are incorporated as charts. You can read this to see how create treemaps from datasets in Excel.
So, you can generate the chart in Excel and pass it to PowerPoint:
//Start an hidden excel application
var appExcel = new Excel.Application { Visible = false };
var workbook = appExcel.Workbooks.Add();
var sheet = workbook.ActiveSheet;
//Generate some random data
Random r = new Random();
for (int i = 1; i <= 10; i++)
{
sheet.Cells[i, 1].Value2 = ((char)('A' + i - 1)).ToString();
sheet.Cells[i, 2].Value2 = r.Next(1, 20);
}
//Select the data to use in the treemap
var range = sheet.Cells.Range["A1", "B10"];
range.Select();
range.Activate();
//Generate the chart
var shape = sheet.Shapes.AddChart2(-1, (Office.XlChartType)117, 200, 25, 300, 300, null);
shape.Chart.ChartTitle.Caption = "Generated TreeMap Chart";
//Copy the chart
shape.Copy();
appExcel.Quit();
//Start a Powerpoint application
var appPpoint = new Point.Application { Visible = Office.MsoTriState.msoTrue };
var presentation = appPpoint.Presentations.Add();
//Add a blank slide
var master = presentation.SlideMaster;
var slide = presentation.Slides.AddSlide(1, master.CustomLayouts[7]);
//Paste the treemap
slide.Shapes.Paste();
Treemap chart in the slide:
Probably you can generate the treemap using the first part (Excel part) and paste the chart using the tool you said, or save the Powerpoint file with the chart generated in VSTO and open it with the tool.
The benefits are that these objects are real charts not just images, so you can change or add colors, styles, effects easily.

Procedural Island Terrain Generation

Edit: Rewrote my question after trying a few things and made it more specific.
Hi, so I'm creating a mobile RTS game with procedurally generated maps. I've worked out how to create a terrain with a basic perlin noise on it, and tried to integrate https://gamedev.stackexchange.com/questions/54276/a-simple-method-to-create-island-map-mask method to creating an island procedurally. This is the result so far:
The image below from http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/ shows the kind of terrain I'm after. The tutorial there is great but would be too intensive, thus the post.
I want the Random Shaped island with Perlin noise generated land mass, surrounded by water.
edit: Basic Perlin terrain gen working now =)
Here is my code. A script attached to a null with a button to activate Begin():
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
public class Gen_Perlin : MonoBehaviour {
public float Tiling = 0.5f;
private bool active = false;
public int mapHeight = 10;
public void Begin()
{
if (active == false) {
TerrainData terrainData = new TerrainData ();
const int size = 513;
terrainData.heightmapResolution = size;
terrainData.size = new Vector3 (2000, mapHeight, 2000);
terrainData.heightmapResolution = 513;
terrainData.baseMapResolution = 1024;
terrainData.SetDetailResolution (1024, 1024);
Terrain.CreateTerrainGameObject (terrainData);
GameObject obj = GameObject.Find ("Terrain");
obj.transform.parent = this.transform;
if (obj.GetComponent<Terrain> ()) {
GenerateHeights (obj.GetComponent<Terrain> (), Tiling);
}
} else {
GameObject obj = GameObject.Find ("Terrain");
if (obj.GetComponent<Terrain> ()) {
GenerateHeights (obj.GetComponent<Terrain> (), Tiling);
}
}
}
public void GenerateHeights(Terrain terrain, float tileSize)
{
Debug.Log ("Start_Height_Gen");
float[,] heights = new float[terrain.terrainData.heightmapWidth, terrain.terrainData.heightmapHeight];
for (int i = 0; i < terrain.terrainData.heightmapWidth; i++)
{
for (int k = 0; k < terrain.terrainData.heightmapHeight; k++)
{
heights[i, k] = 0.25f + Mathf.PerlinNoise(((float)i / (float)terrain.terrainData.heightmapWidth) * tileSize, ((float)k / (float)terrain.terrainData.heightmapHeight) * tileSize);
heights[i, k] *= makeMask( terrain.terrainData.heightmapWidth, terrain.terrainData.heightmapHeight, i, k, heights[i, k] );
}
}
terrain.terrainData.SetHeights(0, 0, heights);
}
public static float makeMask( int width, int height, int posX, int posY, float oldValue ) {
int minVal = ( ( ( height + width ) / 2 ) / 100 * 2 );
int maxVal = ( ( ( height + width ) / 2 ) / 100 * 10 );
if( getDistanceToEdge( posX, posY, width, height ) <= minVal ) {
return 0;
} else if( getDistanceToEdge( posX, posY, width, height ) >= maxVal ) {
return oldValue;
} else {
float factor = getFactor( getDistanceToEdge( posX, posY, width, height ), minVal, maxVal );
return oldValue * factor;
}
}
private static float getFactor( int val, int min, int max ) {
int full = max - min;
int part = val - min;
float factor = (float)part / (float)full;
return factor;
}
public static int getDistanceToEdge( int x, int y, int width, int height ) {
int[] distances = new int[]{ y, x, ( width - x ), ( height - y ) };
int min = distances[ 0 ];
foreach( var val in distances ) {
if( val < min ) {
min = val;
}
}
return min;
}
}
Yeah. The article in question is using a waaay complex method.
The best way of doing this is to take a function that represents the shape of your basic island, with height values between 0 and 1. For the type of island in the picture, you'd basically want something which smoothly rises from the edges, and smoothly dips back to zero where you want lakes.
Now you either add that surface to your basic fractal surface (if you want to preserve spikiness at low elevations) or you multiply it (if you want lower elevations to be smooth). Then you define a height, below which is water.
Here is my very quick go at doing this, rendered with Terragen:
I used a function that rises in a ring from the edge of the map to halfway to the middle, then drops again, to match a similar shape to the one from the article. In practice, you might only use this to get the shape of the island, and then carve the bit of terrain that matches the contour, and bury everything else.
I used my own fractal landscape generator as described here: https://fractal-landscapes.co.uk for the basic fractal.
Here is the C# code that modifies the landscape:
public void MakeRingIsland()
{
this.Normalize(32768);
var ld2 = (double) linearDimension / 2;
var ld4 = 4 / (double) linearDimension;
for (var y = 0u; y < linearDimension; y++)
{
var yMul = y * linearDimension;
for (var x = 0u; x < linearDimension; x++)
{
var yCoord = (y - ld2) * ld4;
var xCoord = (x - ld2) * ld4;
var dist = Math.Sqrt(xCoord * xCoord + yCoord * yCoord);
var htMul = dist > 2 ? 0 :
(dist < 1 ?
dist + dist - dist * dist :
1 - (dist - 1) * (dist - 1));
var height = samples[x + yMul];
samples[x + yMul] = (int) (height + htMul * 32768);
}
}
}
the image you are showing comes from article describing how to generate it

XNA Isometric tile collision

Im a C#/XNA student and I've recently been working on an isometric tile engine and so far it works fairly well. But im having problem trying to figure out on how to do collision, this is what my tile engine does at the moment:
Draws the world from an image and place a tile depending on what color is on my image. For instance color red would draw a grass tile. (Tiles are 64x32)
Camera following player, and my draw loop only draws what the camera sees.
This is how my game looks if that would be of any help:
I don't know what sort of collision would work best. Should i do collision points, or intersects or any other sort of collision. I've read somewhere that you could do Worldtoscreen/Screentoworld but im far to inexperienced and don't know how that works nor how the code would look like.
Here is my code drawing tiles etc:
class MapRow
{
public List<MapCell> Columns = new List<MapCell>();
}
class TileMap
{
public List<MapRow> Rows = new List<MapRow>();
public static Texture2D image;
Texture2D tileset;
TileInfo[,] tileMap;
Color[] pixelColor;
public TileMap(string TextureImage, string Tileset)
{
tileset = Game1.Instance.Content.Load<Texture2D>(Tileset);
image = Game1.Instance.Content.Load<Texture2D>(TextureImage);
pixelColor = new Color[image.Width * image.Height]; // pixelColor array that is holding all pixel in the image
image.GetData<Color>(pixelColor); // Save all the pixels in image to the array pixelColor
tileMap = new TileInfo[image.Height, image.Width];
int counter = 0;
for (int y = 0; y < image.Height; y++)
{
MapRow thisRow = new MapRow();
for (int x = 0; x < image.Width; x++)
{
tileMap[y, x] = new TileInfo();
if (pixelColor[counter] == new Color(0, 166, 81))
{
tileMap[y, x].cellValue = 1;//grass
}
if (pixelColor[counter] == new Color(0, 74, 128))
{
tileMap[y, x].cellValue = 2;//water
}
if (pixelColor[counter] == new Color(255, 255, 0))
{
tileMap[y, x].cellValue = 3;//Sand
}
tileMap[y, x].LoadInfoFromCellValue();//determine what tile it should draw depending on cellvalue
thisRow.Columns.Add(new MapCell(tileMap[y, x]));
counter++;
}
Rows.Add(thisRow);
}
}
public static int printx;
public static int printy;
public static int squaresAcross = Settings.screen.X / Tile.TileWidth;
public static int squaresDown = Settings.screen.Y / Tile.TileHeight;
int baseOffsetX = -32;
int baseOffsetY = -64;
public void draw(SpriteBatch spriteBatch)
{
printx = (int)Camera.Location.X / Tile.TileWidth;
printy = (int)Camera.Location.Y / Tile.TileHeight;
squaresAcross = (int)Camera.Location.X / Tile.TileWidth + Settings.screen.X / Tile.TileWidth;
squaresDown = 2*(int)Camera.Location.Y / Tile.TileHeight + Settings.screen.Y / Tile.TileHeight + 7;
for (printy = (int)Camera.Location.Y / Tile.TileHeight; printy < squaresDown; printy++)
{
int rowOffset = 0;
if ((printy) % 2 == 1)
rowOffset = Tile.OddRowXOffset;
for (printx = (int)Camera.Location.X / Tile.TileWidth; printx < squaresAcross; printx++)
{
if (tileMap[printy, printx].Collides(MouseCursor.mousePosition))
Console.WriteLine(tileMap[printy, printx].tileRect);
foreach (TileInfo tileID in Rows[printy].Columns[printx].BaseTiles)
{
spriteBatch.Draw(
tileset,
tileMap[printy, printx].tileRect = new Rectangle(
(printx * Tile.TileStepX) + rowOffset + baseOffsetX,
(printy * Tile.TileStepY) + baseOffsetY,
Tile.TileWidth, Tile.TileHeight),
Tile.GetSourceRectangle(tileID.cellValue),
Color.White,
0.0f,
Vector2.Zero,
SpriteEffects.None,
tileID.drawDepth);
}
}
}
}
}
Why don't you just draw stuff just like in normal tile based games, and then rotate the camera with a 45degree? Of course then you'd need to make your graphics a bit odd, but would be easier to handle the tiles.
But if you prefer your way, then I'd suggest using simple math to calculate the "tile to the right", "tile to the left" , "tile to the up" and "tile to the down" ones, you know, the tiles around the player(or another tile). You can simply work with your lists, and with some math, basic math, like getting the next tile, is quite simple.
Edit:
You could get the player's next position's tile value with a code something like this:
tileMap[Math.Floor((player.y+playerVelociy.Y)/tileHeight)]
[Math.Floor((player.x+playerVelocity.X)/tileWidth)]
In this code, I assume that the first tile is at 0,0 and you're drawing to right and down. (If not, then just change the Math.Floor to Math.Ceil)
THIS link could help you get the idea, however it's in AS3.0, only the syntax is different.

Image warping in .NET

I was searching for a stretch like warp algorithm to apply in my Windows 8 store application.
I found this android (java) code below.
Most things I can port to C# but some android sdk specific things are blurry for me to port to .NET code.
For example the call too : canvas.drawBitmapMesh , is there some .NET counterpart for this?
The Matrix classes in .NET are also a bit different I think but I can figure that out I think.
Any tips on helping converting/porting the code below to .NET are very welcome.
public class BitmapMesh extends GraphicsActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private static final int WIDTH = 20;
private static final int HEIGHT = 20;
private static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);
private final Bitmap mBitmap;
private final float[] mVerts = new float[COUNT*2];
private final float[] mOrig = new float[COUNT*2];
private final Matrix mMatrix = new Matrix();
private final Matrix mInverse = new Matrix();
private static void setXY(float[] array, int index, float x, float y) {
array[index*2 + 0] = x;
array[index*2 + 1] = y;
}
public SampleView(Context context) {
super(context);
setFocusable(true);
mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.beach);
float w = mBitmap.getWidth();
float h = mBitmap.getHeight();
// construct our mesh
int index = 0;
for (int y = 0; y <= HEIGHT; y++) {
float fy = h * y / HEIGHT;
for (int x = 0; x <= WIDTH; x++) {
float fx = w * x / WIDTH;
setXY(mVerts, index, fx, fy);
setXY(mOrig, index, fx, fy);
index += 1;
}
}
mMatrix.setTranslate(10, 10);
mMatrix.invert(mInverse);
}
#Override protected void onDraw(Canvas canvas) {
canvas.drawColor(0xFFCCCCCC);
canvas.concat(mMatrix);
canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, mVerts, 0,
null, 0, null);
}
private void warp(float cx, float cy) {
final float K = 10000;
float[] src = mOrig;
float[] dst = mVerts;
for (int i = 0; i < COUNT*2; i += 2) {
float x = src[i+0];
float y = src[i+1];
float dx = cx - x;
float dy = cy - y;
float dd = dx*dx + dy*dy;
float d = FloatMath.sqrt(dd);
float pull = K / (dd + 0.000001f);
pull /= (d + 0.000001f);
// android.util.Log.d("skia", "index " + i + " dist=" + d + " pull=" + pull);
if (pull >= 1) {
dst[i+0] = cx;
dst[i+1] = cy;
} else {
dst[i+0] = x + dx * pull;
dst[i+1] = y + dy * pull;
}
}
}
private int mLastWarpX = -9999; // don't match a touch coordinate
private int mLastWarpY;
#Override public boolean onTouchEvent(MotionEvent event) {
float[] pt = { event.getX(), event.getY() };
mInverse.mapPoints(pt);
int x = (int)pt[0];
int y = (int)pt[1];
if (mLastWarpX != x || mLastWarpY != y) {
mLastWarpX = x;
mLastWarpY = y;
warp(pt[0], pt[1]);
invalidate();
}
return true;
}
}
}
There is no direct equivalent in Windows Store apps to be able to draw a texture mapped mesh (and represent image warping and distortion that way). You will have to use Direct3D to draw the texture mapped mesh onto your page.
If you're reluctant to mix C++ and C# in your app, you have two choices.
SharpDX - these are .NET bindings to DirectX.
MonoGame - this is an XNA-compliant, cross-platform graphics interface that uses DirectX on WinRT.
Hope this helps!

Categories