Add ILPoints to ILPlotCube from user input (by mouse) - c#

I am new to ILNumerics, and I have a problem with some simple activity. I want to add Points to the Plot on user mouse input. User clicked for example point 5,4 on the plot, and this point have to draw on this position.
I had some problems to transform location of a mouse to the plot coordinates, and I found the solution here, but it's not ideal for me. What I have done:
private void ilpGraph_Load(object sender, EventArgs e)
{
var scene = new ILScene();
var plotCube = scene.Add(
new ILPlotCube()
{
new PointsGroup()
});
plotCube.Limits.Set(new Vector3(0, 0, 0), new Vector3(10, 10, 0));
plotCube.MouseClick += plotCube_MouseClick;
ilpGraph.Scene = scene;
}
void plotCube_MouseClick(object sender, ILMouseEventArgs e)
{
var pos = new Vector3(0, 0, 0);
ILGroup plotcubeGroup = sender as ILGroup;
ILGroup group = plotcubeGroup.Children.Where(item => item.Tag == "PointsGroup").First() as ILGroup;
if (group != null)
{
// walk up to the next camera node
Matrix4 trans = group.Transform;
while (!(group is ILCamera) && group != null)
{
group = group.Parent;
// collect all nodes on the path up
trans = group.Transform * trans;
}
if (group != null && (group is ILCamera))
{
// convert args.LocationF to world coords
// The Z coord is not provided by the mouse! -> choose arbitrary value
pos = new Vector3(e.LocationF.X * 2 - 1, e.LocationF.Y * -2 + 1, 0);
// invert the matrix.
trans = Matrix4.Invert(trans);
// trans now converts from the world coord system (at the camera) to
// the local coord system in the 'target' group node (surface).
// In order to transform the mouse (viewport) position, we
// left multiply the transformation matrix.
pos = trans * pos;
}
}
// it's for testing now
var pg = (sender as ILPlotCube).Plots.Children[0];
var t = ((PointsGroup)pg).Points.Positions;
int dc = t.DataCount;
t.Update(dc, 1, new[] { pos.X, pos.Y, 0.0f });
e.Refresh = true;
}
public class PointsGroup : ILGroup
{
private ILPoints _points;
public PointsGroup()
: base((object)"PointsGroup", new Vector3?(), 0.0, new Vector3?(), new Vector3?(), new RenderTarget?())
{
}
internal PointsGroup(PointsGroup source) : base((ILGroup) source)
{
}
public ILPoints Points
{
get
{
if (_points == null)
return new ILPoints();
return _points;
}
set { _points = value; }
}
protected override ILNode CreateSynchedCopy(ILNode source)
{
return (ILNode)new PointsGroup();
}
public override ILNode Copy()
{
return (ILNode)new PointsGroup(this);
}
public override ILNode Synchronize(ILNode copy, ILSyncParams syncParams)
{
return base.Synchronize(copy, syncParams);
}
}
What is the problem:
If I don't define PointsGroup class, I can't localize mouse coordinates on plot (ILPoints is not an ILGroup and I don't know how to do it differently).
With the PointsGroup coordinates are calculated good, but the coordinates are not remembered and not shown on the plot. I know why it happens, but I really don't know how to fix this.
How to extend my PointsGroup class to get my values remembered and show on plot or how to modify coordinates calculation to work on ILPoints objects?
Can anyone help me? Please

Related

Pan canvas such that Point (x,y) is at the center

I have a Canvas is WPF which includes a lot of shapes and lines. I have included Zooming and Panning feature in the canvas. I have a search like feature which takes X and Y as input and when I request for search the canvas should zoom into that point and translate the canvas in such a way that (X,Y) position is at the center. The zooming works fine but I'm not able to implement the Pan.
I used the following code for PanBorder.cs with the help of this post. Here, I want use PanToPosition(double x , double y) to translate such that the center is at (X,Y).
class PanBorder : Border
{
private UIElement child = null;
private Point origin;
private Point start;private TranslateTransform GetTranslateTransform(UIElement element)
{
return (TranslateTransform)((TransformGroup)element.RenderTransform)
.Children.First(tr => tr is TranslateTransform);
}
private ScaleTransform GetScaleTransform(UIElement element)
{
return (ScaleTransform)((TransformGroup)element.RenderTransform)
.Children.First(tr => tr is ScaleTransform);
}
public override UIElement Child
{
get { return base.Child; }
set
{
if (value != null && value != this.Child)
this.Initialize(value);
base.Child = value;
}
}
public void Initialize(UIElement element)
{
this.child = element;
if (child != null)
{
TransformGroup group = new TransformGroup();
ScaleTransform st = new ScaleTransform();
group.Children.Add(st);
TranslateTransform tt = new TranslateTransform();
group.Children.Add(tt);
child.RenderTransform = group;
child.RenderTransformOrigin = new Point(0.0, 0.0);
this.MouseLeftButtonDown += child_MouseLeftButtonDown;
this.MouseLeftButtonUp += child_MouseLeftButtonUp;
this.MouseMove += child_MouseMove;
}
}
public void Reset()
{
if (child != null)
{
// reset pan
var tt = GetTranslateTransform(child);
tt.X = 0.0;
tt.Y = 0.0;
}
}
public void PanToPosition(double x, double y) {
if (child != null)
{
var tt = GetTranslateTransform(child);
//Pan such that center is at (X,Y)
}
}
#region Child Events
private void child_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (child != null)
{
var tt = GetTranslateTransform(child);
start = e.GetPosition(this);
origin = new Point(tt.X, tt.Y);
this.Cursor = Cursors.Hand;
child.CaptureMouse();
}
}
private void child_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (child != null)
{
child.ReleaseMouseCapture();
this.Cursor = Cursors.Arrow;
}
}
private void child_MouseMove(object sender, MouseEventArgs e)
{
if (child != null)
{
if (child.IsMouseCaptured)
{
var tt = GetTranslateTransform(child);
Vector v = start - e.GetPosition(this);
tt.X = origin.X - v.X;
tt.Y = origin.Y - v.Y;
}
}
}
#endregion
}`
I have the following code to zoom into the canvas:
//for zooming control on the window
void window_MouseWheel(object sender, MouseWheelEventArgs e)
{
Point p = e.MouseDevice.GetPosition(canvasWaSNA);
Matrix m = canvasWaSNA.RenderTransform.Value;
if (e.Delta > 0)
m.ScaleAtPrepend(1.1, 1.1, p.X, p.Y);
else
m.ScaleAtPrepend(1 / 1.1, 1 / 1.1, p.X, p.Y);
canvasWaSNA.RenderTransform = new MatrixTransform(m);
}
And when I input the Point (X,Y) I call the following function:
public void moveToPosition(double x, double y) {
resize();
border.PanToPosition(x, y);
Point p = new Point(x, y);
Matrix m = canvasWaSNA.RenderTransform.Value;
m.ScaleAtPrepend(6, 6, p.X, p.Y);
canvasWaSNA.RenderTransform = new MatrixTransform(m);
}
To resize the canvas I have following code:
private void resize()
{
Matrix m = canvasWaSNA.RenderTransform.Value;
m.SetIdentity();
canvasWaSNA.RenderTransform = new MatrixTransform(m);
border.Reset();
}
I need help. Thanx in advance.
Well, I finally did it. I applied the simple concept of translating a point, as my problem was to translate Point(X,Y) to the center of the screen and apply the zoom. Here is the updated PanToPosition function:
public void PanToPosition(double x, double y, Point center) {
if (child != null)
{
var tt = GetTranslateTransform(child);
start = new Point(x,y);
Point p = new Point(center.X, center.Y);
origin = new Point(tt.X, tt.Y);
Vector v = start - p;
tt.X = origin.X - v.X;
tt.Y = origin.Y - v.Y;
}
}
And I called this function as:
public void moveToPosition(double x, double y) {
resize();
Point center = new Point(this.Width / 2, this.Height / 2);
border.PanToPosition(x, y, center);
Point p = new Point(x, y);
Matrix m = canvasWaSNA.RenderTransform.Value;
m.ScaleAtPrepend(6, 6, p.X, p.Y);
canvasWaSNA.RenderTransform = new MatrixTransform(m);
}
Here, I passed the center point of the screen and the point to be translated to the center. The start point is the point to be translated and p is the destination. Thus, I calculated the translation scale factor.

Plotting data given from a serial port in ILNumerics

I am currently working dearlessly making a Win32 (.net) program that plots data coming from the serial port. I have been able to get the data from the serial port, and set up the ilnumerics plot cube, etc. But what I can't do is convert the (x,y,z) to one ILAray to plot points.
public void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (serialPort1.IsOpen == true)
{
Varriables.numberOfSerialBytes = serialPort1.BytesToRead;
if (Varriables.numberOfSerialBytes >= Varriables.numberOfSerialBytesPerLine) //change 13 to number of bytes, changes for number of stars (stars x3 x BytesPerInt)
{
string serialText = serialPort1.ReadExisting();
if (serialText != "") RecievedText(serialText);
else { }
}
}
}
public void RecievedText(string text)
{
if ( richTextBox1.InvokeRequired)
{
SetTextCallBack x = new SetTextCallBack(RecievedText);
this.Invoke(x, new object[] { text });
}
else
{
richTextBox1.Text = text;
try { CalculatePoints(text); }
catch { }
}
}
public void CalculatePoints(string positionsString)
{
string[] starpositionStringList = positionsString.Split(" ".ToCharArray());
List<float> starPositionFloatListX = new List<float>();
List<float> starPositionFloatListY = new List<float>();
List<float> starPositionFloatListZ = new List<float>();
System.Console.WriteLine(starpositionStringList.Count());
for (int i = 0; i < starpositionStringList.Count() / 3; i += 3)
{
int iy = i + 1;
int iz = i + 2;
float x = float.Parse(starpositionStringList[i]);
float y = float.Parse(starpositionStringList[iy]);
float z = float.Parse(starpositionStringList[iz]);
starPositionFloatListX.Add(x);
starPositionFloatListY.Add(y);
starPositionFloatListZ.Add(z);
}
float[] xArray = starPositionFloatListX.ToArray<float>();
float[] yArray = starPositionFloatListY.ToArray<float>();
float[] zArray = starPositionFloatListZ.ToArray<float>();
PlotPoints(xArray, yArray, zArray);
}
public void PlotPoints(float[] x, float[] y, float[] z)
{
//could send data through PlotPoints(x,y,z);
//or make a long array using a for loop and send that
//either way i still need to make an array to plot and use ilPlanel2.Scene = Scene to continue upadating the scene
ILArray<float> starPositionsX = x;
ILArray<float> starPositionsY = y;
ILArray<float> starPositionsZ = z;
ILArray<float> starPositions = ???????????????; //need to convert (x,y,z) to ILArray<float>
ilPanel2.Scene.First<ILPlotCube>().Animations.AsParallel();
if(Varriables.pointsPlotted == false)
{
//ilPanel2.Scene.First<ILPlotCube>(){ new ILPoints { Positions = starPositions, Colors } };
//Cube.Add(new ILPoints { Positions = starPositions });
ilPanel2.Scene.First<ILPlotCube>().Add<ILPoints>(new ILPoints{ Positions = starPositions });
Varriables.pointsPlotted = true;
}
else
{
ilPanel2.Scene.First<ILPoints>().Positions.Update(starPositions);
}
ilPanel2.Refresh();
}
is there a IL.Math function that I can use to do this?
Create an ILArray of the needed size and use the incoming float[] System.Arrays directly in order to fill the values:
public void PlotPoints(float[] x, float[] y, float[] z) {
using (ILScope.Enter()) {
ILArray<float> starPositions = ILMath.zeros<float>(3, x.Length);
starPositions["0;:"] = x;
starPositions["1;:"] = y;
starPositions["2;:"] = z;
ilPanel1.Scene.First<ILPlotCube>().Add<ILPoints>(new ILPoints { Positions = starPositions });
// (... or update accordingly, if the shape exists already)
// at the end of all modifications, call Configure()
ilPanel1.Scene.Configure();
ilPanel1.Refresh();
}
}
Don't forget to call Configure() on the modified shape (or any parent node on the path to the root) afterwards! Also, I usually wrap all ILArray related code in an using (ILScope.Enter()) { .. block. It helps to increase the performance for frequent updates significantly.

2D Per pixel collision detection not working

I would like to say sorry in advance for the amount of code I will post, but I can't seem to get my collision detection to work, the player and the objects pass through each other with no effect when I play test.
I receive 0 warnings or errors, but the Player playerBot object does not seem to interact with any of the level items I have imported from GLEED2D.
One thing I did was to store the values of my item's rectangles and color arrays in lists so they could be all easily iterated through, maybe this is the source of the problem.
If you can spot why my code is not working I will be hugely grateful. I have removed any code that is definitely not relevant, and I am running VS 2010 with GLEED2D 1.3 if that helps.
Thanks again.
// Item Class ImageItem Downcast
public class ImageItem : Item
{
public Texture2D Texture;
}
// Level
Level level;
// Ints
int iNumOfItems = 0;
int iTextureDataListNum = 0;
int iRectangleListNum = 0;
// Lists
List<Color []> itemTextureDataList = new List<Color[]>();
List<Rectangle> itemRectangleList = new List<Rectangle>();
protected override void Initialize()
{
if (filename.Length > 0) level = Level.FromFile(filename, Content);
else level = Level.FromFile("level1.xml", Content);
foreach (Layer layer in level.Layers)
{
foreach (Item item in layer.Items)
{
iNumOfItems =+ 1;
}
}
// Creates Player Ship
playerBot = new Player(new Vector2(400f, 240f), new Vector2(0f, 0f));
base.Initialize();
}
protected override void LoadContent()
{
Texture2D pixel = new Texture2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
pixel.SetData(new[] { Color.White });
spriteBatch = new SpriteBatch(GraphicsDevice);
// Player Bot
playerBot.LoadContent(Content, "Images/Player Bot Sprite Sheet", 40, 40, 4);
// Assigns level textures color data to array
foreach (Layer layer in level.Layers)
{
foreach (Item item in layer.Items)
{
ImageItem imageItem = item as ImageItem;
if (imageItem != null)
{
Texture2D texture = imageItem.Texture;
itemTextureDataList[iTextureDataListNum] = new Color[imageItem.Texture.Width * imageItem.Texture.Height];
imageItem.Texture.GetData(itemTextureDataList[iTextureDataListNum]);
iTextureDataListNum++;
}
}
}
// Creates a rectangle for every level texture
foreach (Layer layer in level.Layers)
{
foreach (Item item in layer.Items)
{
ImageItem imageItem = item as ImageItem;
if (imageItem != null)
{
itemRectangleList[iRectangleListNum] = new Rectangle((int)imageItem.Position.X, (int)imageItem.Position.Y, imageItem.Texture.Width, imageItem.Texture.Height);
iRectangleListNum++;
}
}
}
spriteBatch = new SpriteBatch(GraphicsDevice);
}
protected override void Update(GameTime gameTime)
{
// Player Update
playerBot.Update(gameTime);
((Sprite)playerBot).Update(gameTime);
// Check for player collisons with level
for (int i = 0; i < iNumOfItems - 1; i++)
{
if (IntersectPixels(playerBot.colRectangle, playerBot.textureDataArray, itemRectangleList[i], itemTextureDataList[i]) == true)
{
playerBot.StopMovement();
}
}
base.Update(gameTime);
}
// Level Collision Detection Method
static bool IntersectPixels(Rectangle rectangleA, Color[] dataA, Rectangle rectangleB, Color[] dataB)
{
// Find the bounds of the rectangle intersection
int top = Math.Max(rectangleA.Top, rectangleB.Top);
int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
int left = Math.Max(rectangleA.Left, rectangleB.Left);
int right = Math.Min(rectangleA.Right, rectangleB.Right);
// Check every point within the intersection bounds
for (int y = top; y < bottom; y++)
{
for (int x = left; x < right; x++)
{
// Get the color of both pixels at this point
Color colorA = dataA[(x - rectangleA.Left) + (y - rectangleA.Top) * rectangleA.Width];
Color colorB = dataB[(x - rectangleB.Left) + (y - rectangleB.Top) * rectangleB.Width];
// If both pixels are not completely transparent
if (colorA.A != 0 && colorB.A != 0)
{
// Then an intersection has been found
return true;
}
}
}
// No intersection fond
return false;
}
// Sprite Class
public void Update(GameTime gameTime)
{
textureDataArray = new Color[texture.Width * texture.Height];
texture.GetData(textureDataArray);
// Player Class
public void StopMovement()
{
velocity.X *= -1;
velocity.Y *= -1;
}
The first thing I'll say here is, you should aim to be more Object Oriented in your approach. Storing a list of textures and a list of rectangles alongside your list of Items is "bad" technique and going to eventually cause you some massive headaches when it comes to debugging.
So first of all, instead of having a list of Color[] and a list of Rectangle, add one Color[] and one Rectangle to your ImageItem class and work with those instead, or at least create a little class called "CollisionData" that has a Rectangle and a Color[] and store those in a single list.
Secondly, note that there is a Rectangle.Intersect(Rectangle A, Rectangle B) that gets your the rectangle of intersection. So you can tidy up your code a bit by using that.
Your color checking can be simplified to (ColorA.A * ColorB.A != 0) as either being zero will
cause the result to be zero.
Regarding not getting any errors, put a breakpoint at the start of the collision checking loop. Does the application break? If yes, what is the value of iNumItems? (you can hover over it to see the current value at point of breaking). If no, then that section of code isn't being reached. Put another breakpoint a bit further back and a bit further back until it gets hit, then figure out why the code isn't executing.

Tile Rectangle Collisions

I have a map that is 1280 by 1280 pixels.
I am drawing tiles on the screen that are 32 by 32 pixels.
I also give each tile a rectangle of 32 by 32 pixels.
Each tile also has a bool telling me if it is blocked or not.
My sprite has a rectangle of 32 by 32 pixels.
When the sprite and tile intersect the top left most pixel of the tile is the only one that makes contact with the sprites rectangle.
I'm tried inflating the rectangle with no change.
Core.playerOneSpriteRectangle = new Rectangle((int)Core.playerOneSprite.Position.X, (int)Core.playerOneSprite.Position.Y, Engine.TileWidth, Engine.TileHeight);
#region Player One Controls
// Player One Controls
Tile tile;
Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);
if (keyState.IsKeyDown(Keys.W) || (gamePadOneState.DPad.Up == ButtonState.Pressed))
{
Core.playerOneSprite.CurrentAnimation = AnimationKey.Up;
foreach (var layer in tileMapLayers)
{
for (var y = 0; y < layer.Height; y++)
{
destination.Y = y * Engine.TileHeight;
for (var x = 0; x < layer.Width; x++)
{
tile = layer.GetTile(x, y);
destination.X = x * Engine.TileWidth;
// Check Collision With Tiles
if (Core.playerOneSpriteRectangle.Intersects(destination) && tile.TileBlocked)
{
playerOneMotion.Y = destination.Bottom;
//Console.WriteLine("Intersected with" + destination + tile.TileBlocked);
}
else if (Core.playerOneSpriteRectangle.Intersects(destination) && tile.TileBlocked == false)
{
playerOneMotion.Y = -1f;
}
}
}
}
}
I'm trying to create rectangles for each tile so my sprite doesn't walk through them.
UPDATE
This is the code I currently have after modifying it with suggestions.
Tile tile;
Point playertile = new Point((int)Core.playerOneSprite.Position.X / Engine.TileWidth, (int)Core.playerOneSprite.Position.Y / Engine.TileHeight);
if (keyState.IsKeyDown(Keys.W) || (gamePadOneState.DPad.Up == ButtonState.Pressed))
{
Core.playerOneSprite.CurrentAnimation = AnimationKey.Up;
foreach (var layer in GraelenAreaOne.graelenAreaOneSplatterTileMapLayers)
{
tile = layer.GetTile(playertile.X, playertile.Y - 1);
// Check Collision With Tiles
if (tile.TileBlocked)
{
playerOneMotion.Y = 0;
//Console.WriteLine("Intersected with" + destination + tile.TileBlocked);
}
else
{
playerOneMotion.Y = -1;
}
}
}
I am now able to collide with tiles however my sprites rectangle is not intersecting properly. Position 0,0 of the sprite texture is in the top left most corner and it only a 1x1 pixel.
Why isn't the rectangle encompassing the entire texture as I have it set to 32x32?
You should just check whether the tile you want to go to is walkable or not, without having to bother yourself with rectangles.
Some pseudo code :
// Convert pixel coordinates to tile coords
Point playerTile = new Point(player.X / tileWidth, player.Y / tileHeight);
Point targetTile = new Point(target.X / tileWidth, target.Y / tileHeight);
// Take a decision
if(Direction == Up)
var tile = layer.GetTile(playerTile.X, playerTile.Y - 1);
if(tile == walkable)
// Move to your tile
else
...
Additionally, here's some code I wrote a while ago which might interest you in optimizing the drawing of your level by drawing only draws what's visible.
https://gamedev.stackexchange.com/questions/29121/organize-a-game-set/29930#29930
Note : https://gamedev.stackexchange.com/ is definitely a better place for these kind of questions.
EDIT
Here's a quick example that works for me :
Note that I return when player cannot move to tile.
Tiles : 0 is walkable, 1 is a wall, 2 is player
using System;
using System.Linq;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input.Touch;
using Point = Microsoft.Xna.Framework.Point;
namespace SlXnaApp1
{
public partial class GamePage : PhoneApplicationPage
{
private readonly GameTimer _timer;
private int[] _levels;
private Point _player;
private Texture2D _t1;
private Texture2D _t2;
private Texture2D _t3;
private Texture2D[] _textures;
private ContentManager _contentManager;
private SpriteBatch _spriteBatch;
public GamePage()
{
InitializeComponent();
// Get the content manager from the application
_contentManager = (Application.Current as App).Content;
// Create a timer for this page
_timer = new GameTimer();
_timer.UpdateInterval = TimeSpan.FromTicks(333333);
_timer.Update += OnUpdate;
_timer.Draw += OnDraw;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Set the sharing mode of the graphics device to turn on XNA rendering
var graphicsDevice = SharedGraphicsDeviceManager.Current.GraphicsDevice;
graphicsDevice.SetSharingMode(true);
// Create a new SpriteBatch, which can be used to draw textures.
_spriteBatch = new SpriteBatch(graphicsDevice);
// TODO: use this.content to load your game content here
_t1 = new Texture2D(graphicsDevice, 32, 32);
_t1.SetData(Enumerable.Repeat(Color.Red, 32*32).ToArray());
_t2 = new Texture2D(graphicsDevice, 32, 32);
_t2.SetData(Enumerable.Repeat(Color.Green, 32*32).ToArray());
_t3 = new Texture2D(graphicsDevice, 32, 32);
_t3.SetData(Enumerable.Repeat(Color.Blue, 32*32).ToArray());
_textures = new[] {_t1, _t2, _t3};
_levels = new int[4*4]
{
2, 0, 0, 0,
0, 0, 1, 0,
1, 1, 1, 0,
0, 0, 0, 1,
};
_player = new Point();
TouchPanel.EnabledGestures = GestureType.Flick;
// Start the timer
_timer.Start();
base.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
// Stop the timer
_timer.Stop();
// Set the sharing mode of the graphics device to turn off XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
base.OnNavigatedFrom(e);
}
/// <summary>
/// Allows the page to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
private void OnUpdate(object sender, GameTimerEventArgs e)
{
Vector2 vector = new Vector2();
while (TouchPanel.IsGestureAvailable)
{
var gestureSample = TouchPanel.ReadGesture();
var vector2 = gestureSample.Delta;
vector2.Normalize();
vector = new Vector2((float) Math.Round(vector2.X), (float) Math.Round(vector2.Y));
}
Direction direction = new Direction();
if (vector.X > 0) direction = Direction.Right;
else if (vector.X < 0) direction = Direction.Left;
else if (vector.Y < 0) direction = Direction.Up;
else if (vector.Y > 0) direction = Direction.Down;
Point newPos = new Point();
switch (direction)
{
case Direction.None:
return;
case Direction.Up:
if (GetTile(_player.X, _player.Y - 1) == 0)
newPos = new Point(_player.X, _player.Y - 1);
else return;
break;
case Direction.Down:
if (GetTile(_player.X, _player.Y + 1) == 0)
newPos = new Point(_player.X, _player.Y + 1);
else return;
break;
case Direction.Left:
if (GetTile(_player.X - 1, _player.Y) == 0)
newPos = new Point(_player.X - 1, _player.Y);
else return;
break;
case Direction.Right:
if (GetTile(_player.X + 1, _player.Y) == 0)
newPos = new Point(_player.X + 1, _player.Y);
else return;
break;
default:
throw new ArgumentOutOfRangeException();
}
SetTile(_player.X, _player.Y, 0);
SetTile(newPos.X, newPos.Y, 2);
_player = newPos;
}
private int GetTile(int x, int y)
{
return _levels[y*4 + x];
}
private void SetTile(int x, int y, int value)
{
_levels[y*4 + x] = value;
}
/// <summary>
/// Allows the page to draw itself.
/// </summary>
private void OnDraw(object sender, GameTimerEventArgs e)
{
SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.CornflowerBlue);
_spriteBatch.Begin();
for (int i = 0; i < _levels.Length; i++)
{
var tile = _levels[i];
Point point = new Point(i%4, i/4);
var texture2D = _textures[tile];
_spriteBatch.Draw(texture2D, Vector2.Multiply(new Vector2(point.X, point.Y), 32), Color.White);
}
_spriteBatch.End();
}
private enum Direction
{
None,
Up,
Down,
Left,
Right
}
}
}
Also, due to the way I built my level as a 1-dimensional array, the tile moves down when for instance it is at x=3, y=0 and going to the right; this is unintended at all but is normal as I don't check bounds. You'd want to keep your level as a 2-dimensional array for simplicity.

Whats an easy way to fill a Concave PathGeometry to be Convex (finding the concave vertices & removing them)?

I've got a PathGeometry (polygon) built up of LineSegments on one PathFigure and I'd like to ensure that it's Convex. I have a method using the CrossProduct to determine whether the geometry is Convex, I was assuming that I could just return back a list of points that make it concave when it's false and remove those points to fill the polygon but it's not working quite right.
Here's the code I've got:
public static bool IsConvexPolygon(this IList<Point> polygon, out List<Point> concavePoints)
{
int n = polygon.Count;
List<double> result = new List<double>();
concavePoints = new List<Point>();
for (int i = 0; i < n; i++)
{
result.Add(polygon[i].CrossProduct(polygon[i.RotateNext(n)]));
if (result.Last() < 0.0)
{
concavePoints.Add(polygon[i.RotateNext(n)]);
}
}
return (result.All(d => d >= 0.0));
}
public static double CrossProduct(this Point p1, Point p2)
{
return (p1.X * p2.Y) - (p1.Y * p2.X);
}
public static int RotateNext(this int index, int count)
{
return (index + 1) % count;
}
public static PointCollection ExtractPoints(this Geometry geometry)
{
PointCollection pc = new PointCollection();
if (geometry is LineGeometry)
{
var lg = (LineGeometry)geometry;
pc.Add(lg.StartPoint);
pc.Add(lg.EndPoint);
return pc;
}
else if (geometry is PathGeometry)
{
var pg = (PathGeometry)geometry;
if (pg.Figures.Count > 0)
{
List<Point> points;
if ((pg.Figures[0].Segments.Count > 0) && (pg.Figures[0].Segments[0] is PolyLineSegment))
points = ((PolyLineSegment)pg.Figures[0].Segments[0]).Points.ToList();
else
points = pg.Figures[0].Segments.Select(seg => (seg as LineSegment).Point).ToList();
pc.Add(pg.Figures[0].StartPoint);
foreach (Point p in points)
pc.Add(p);
return pc;
}
}
else if (geometry is RectangleGeometry)
{
var rg = (RectangleGeometry)geometry;
var rect = rg.Rect;
pc.Add(rect.TopLeft);
pc.Add(rect.TopRight);
pc.Add(rect.BottomRight);
pc.Add(rect.BottomLeft);
return pc;
}
return pc;
}
public static Geometry CreateGeometryFromPoints(this List<Point> pts)
{
if (pts.Count < 2)
return null;
PathFigure pFig = new PathFigure() { StartPoint = pts[0] };
for (int i = 1; i < pts.Count; i++)
{
pFig.Segments.Add(new LineSegment(pts[i], true));
}
pFig.IsClosed = true;
PathGeometry pg = new PathGeometry(new List<PathFigure>() { pFig });
return pg;
}
public static Path CreatePolygonFromGeometry(this Geometry geo, Brush fillBrush)
{
Path path = new Path() { Stroke = Brushes.Black, StrokeThickness = 1, Fill = fillBrush };
path.Data = geo;
return path;
}
And Here's where I making the check and correcting the polygon:
List<Point> outstuff;
if (geo1.ExtractPoints().IsConvexPolygon(out outstuff) == false)
{
// Got to fill it in if it's concave
var newpts = geo1.ExtractPoints().Except(outstuff).ToList();
var z = newpts.CreateGeometryFromPoints().CreatePolygonFromGeometry(Brushes.Purple);
z.MouseRightButtonDown += delegate { canvas.Children.Remove(z); };
canvas.Children.Add(z);
}
Ultimately I'd like to be able to make my Concave Geometry into a Convex one like this:
I'd compute the convex hull (also: NTS) and remove any vertexes on the interior of the resulting convex hull polygon (using a point-in-polygon test).
You cycle through each triplet of adjacent vertices (ABC, BCD, CDE, etc). For each triplet you compute the middle point of the segment linking the first and third vertex (you connect A-C in ABC, B-D in BCD, etc). If the middle point is inside the polygon, you go to the next triplet. If it's outside, you substitute the 2 segments linking the triplet with the one segment linking the extremes (that is, you remove the middle point). You go on until no more substitutions are possible.
If you try it on paper, you get exactly the result you describe.
If I'm not mistaken, you can test if a point belongs to a polygon with Polygon.HitTestCore.

Categories