Implementing snap to canvas gridlines - c#

I'm a bit mind boggled at this, but i have the problem of trying to get my head around mouse snapping to a grid. Currently I'm drawing a grid by overriding OnRender like so;
protected override void OnRender(DrawingContext drawingContext)
{
int numberOfVerticalLines = 8;
int numberOfHorizontalLines = 8;
CellHeight = this.ActualHeight / numberOfVerticalLines;
CellWidth = this.ActualWidth / numberOfHorizontalLines;
double verticalOffset = 0;
double horizontalOffset = 0;
Pen pen = new Pen(Stroke, StrokeThickness);
pen.DashStyle = DashStyle;
for (int i = 0; i <= numberOfHorizontalLines; i++)
{
for (int j = 0; j <= numberOfVerticalLines; j++)
{
drawingContext.DrawLine(pen, new Point(horizontalOffset, verticalOffset), new Point(horizontalOffset, CellHeight + verticalOffset));
verticalOffset += CellHeight;
}
horizontalOffset += CellWidth;
verticalOffset = 0;
}
horizontalOffset = 0;
verticalOffset = 0;
for (int i = 0; i <= numberOfVerticalLines; i++)
{
for (int j = 0; j <= numberOfHorizontalLines; j++)
{
drawingContext.DrawLine(pen, new Point(horizontalOffset, verticalOffset), new Point(CellWidth + horizontalOffset, verticalOffset));
horizontalOffset += CellWidth;
}
verticalOffset += CellHeight;
horizontalOffset = 0;
}
}
And that gives the following result;
However i'm a little stuck with thinking about a route to take to snap the mouse to the nearest grid intersection (where a horizontal line meets a vertical line). Obviously as i'm using the drawingcontext to draw the lines i have no reference to these lines after they've been drawn.
So i guess essentially my question is, how can i go about implementing mouse snap to grid? Is this more of a maths question than an object orientated control question? I've read through almost every stack overflow question i found relevant but havn't been able to come to any realistic ideas yet.
Note: Whilst at the minute i've hard coded an 8x8 grid, this will be user defined eventually.

A basic approach is to compare the (x,y) of your mouse to the crosses by:
1. calculating the start- and endpoint of the width and height of the cell in which the mouse is located; and
2. comparing those two intervals (width and height) to the actual mouse (x,y) to find the nearest cell point.
Here is some quickly thrown together example code to demonstrate the snapping:
/// <summary>
/// When left shift key is pressed we snap the mouse to the closest
/// intersection
/// </summary>
void MainWindow_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.LeftShift)
{
var p = GetSnappingPoint(Mouse.GetPosition(this), new Size(200, 200));
SetCursorPos((int)p.X, (int)p.Y+20);
}
}
[DllImport("User32.dll")]
private static extern bool SetCursorPos(int x, int y);
/// <summary>
/// Get snapping point by
/// </summary>
Point GetSnappingPoint(Point mouse,Size cellSize)
{
//Get x interval based on cell width
var xInterval = GetInterval(mouse.X, cellSize.Width);
//Get y interal based in cell height
var yInterval = GetInterval(mouse.Y, cellSize.Height);
// return the point on cell grid closest to the mouseposition
return new Point()
{
X = Math.Abs(xInterval.Lower - mouse.X) > Math.Abs(xInterval.Upper - mouse.X) ? xInterval.Upper : xInterval.Lower,
Y = Math.Abs(yInterval.Lower - mouse.Y) > Math.Abs(yInterval.Upper - mouse.Y) ? yInterval.Upper : yInterval.Lower,
};
}
/// <summary>
/// Find an interval of the celsize based on mouse position and size
/// </summary>
Interval GetInterval(double mousePos,double size)
{
return new Interval()
{
Lower = ((int)(mousePos / size)) * size,
Upper = ((int)(mousePos / size)) * size + size
};
}
/// <summary>
/// Basic interval class
/// </summary>
class Interval
{
public double Lower { get; set; }
public double Upper { get; set; }
}

A rough start to an answer:
int nearGridX = CellWidth * Math.Round( mouseX / CellWidth);
int nearGridY = CellHeight * Math.Round( mouseY / CellHeight);

xPos= mouseX - (mouseX % gridWidth);
yPos= mouseY - (mouseY % gridHeight);
This would give you a quick and dirty snap to the upper-left corner of the current grid. This doesn't take into account for if you're closer to the bottom or right of the current grid. It only looks at what grid you're in and plops it there.

Related

C# Control Two Motors according to the Mouseclicks on a Picturebox

I am developing a C# win form GUI for controlling a two-motor XY stage. I have drawn a 100 x 100 square grid pattern on a picturebox in which each square, when clicked, represents a coordinate that the two motors must move to. I have studied this link
PictureBox Grid and selecting individual cells when clicked on and this PictureBox- Grids and Filling in squares (Game of Life) for drawing a grid and marking the clicked positions.
Now I have to transform the series of randomly clicked points to actual movement of the two motors.
How shall I translate the click coordinates programmatically to give commands to control the motors?
I know how to move and control the motors without referring to the screen coordinates, i.e. by using eyes.
Thank you very much for your kind help.
Update1:
Hello... I think I am thinking too much in a confusing way to move the motors from one point to another despite Sebastien's great help. I wanted to try some logic below but I appreciate if somebody can enlighten me how best to implement this.
private void pictureBoxGrid_MouseClick(object sender, MouseEventArgs e)
{
//int x = e.X;
int x = cellSize * (e.X / cellSize);
int y = cellSize * (e.Y / cellSize);
int i = x / 8; // To limit the value to below 100
int j = y / 8;
// Reverse the value of fill_in[i, j] - if it was false, change to true,
// and if true change to false
fill_in[i, j] = !fill_in[i, j];
if (fill_in[i, j])
{
//Save the coordinate in a list
filledSq.Add(new Point(i, j));
using (Graphics g = Graphics.FromImage(buffer))
{
g.FillRectangle(Brushes.Black, x + 1, y + 1, 7, 7);
}
}
else
{
//Delete the coordinate in a list
filledSq.Remove(new Point(i, j));
Color customColor = SystemColors.ControlLightLight;
using (Graphics g = Graphics.FromImage(buffer))
using (SolidBrush shadowBrush = new SolidBrush(customColor))
{
g.FillRectangle(shadowBrush, x + 1, y + 1, 7, 7);
}
}
//pictureBoxGrid.BackgroundImage = buffer;
pictureBoxGrid.Invalidate();
}
private void buttonSavePoints_Click(object sender, EventArgs e)
{
// to be implemented...
}
private void buttonRun_Click(object sender, EventArgs e)
{
var noOfDots = filledSq.Count;
filledSq = filledSq.OrderBy(p => p.X).ThenBy(p => p.Y).ToList();
var motor = new Motor();
for (var i = 0; i < noOfDots; i++)
{
motor.Move(filledSq[i].X, filledSq[i].Y); //call the motor to move to X,Y here?
//do sth at each position
}
}
Since you wrote, that you know how to move the motors programmatically this answer will be more theoretical:
Each steppermotor has a predefined anglewidth per step (e.g. 1.8°).
And if you know where your Motors are (for example at a predefined starting point with limitswitches (0|0)) you can calculate where they need to be.
For the precision there are multiple factors like if you are using Belts or threaded rods.
An examplemethod could look like this:
private static float stepwidth = 1.8;
private static beltConverionPerDegree = 0.2; // Or rod
private float currentPositionX = 0;
private float currentPositionY = 0;
public Tuple<int, int> GetSteps(float x, float y) {
// calculate the position relative to the actual position (Vector between two points)
float relativeX = x - currentPositionX;
float relativeY = y - currentPositionY;
return new Tuple<int, int> (relativeX / (stepwidth * beltConverionPerDegree), relativeY / (stepwidth * beltConverionPerDegree));
}
beltConverionPerDegree means how much distance your motor moves the belt for each degree.

C# chart using crosshair cursor

i'm working with C# chart class to display a curve. the type of the series is spline.
the thing would i do is show a crosshair cursor in the chart area. the vertical line of this cursor should move with the mouse when the mouse enter the chart. but the horizontal line should move whit the curve line not with mouse movement.
the code which i typed is here:
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
Point mousePoint = new Point(e.X, e.Y);
chart1.ChartAreas[0].CursorX.SetCursorPixelPosition(mousePoint,false);
chart1.ChartAreas[0].CursorY.SetCursorPixelPosition(mousePoint,false);
}
this code cause the cursor move whith the mouse and the result is like in this image:
to make the horizontal line of the cursor follow the curve line which is a sinusoidal signal here. i should know the Point of intersection between the vertical line of the cursor and the curve line. as this picture show:
is there any direct way to find this point? any help plz!.
You can get the y pixel-offset relative to the top of the chart area if you know the following:
height = Chart height in pixels
rangeMin/rangeMax = Chart range min/max (here -150/150)
value = function value (bottom image, approx. 75)
Taking the following formula:
yOffset = height * (rangeMax - value) / (rangeMax - rangeMin);
You should be able to plug yOffset into your MouseMove function like so:
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
double yOffset = GetYOffset(chart1, e.X);
Point mousePoint = new Point(e.X, yOffset);
chart1.ChartAreas[0].CursorX.SetCursorPixelPosition(mousePoint,false);
chart1.ChartAreas[0].CursorY.SetCursorPixelPosition(mousePoint,false);
}
// ChartClass chart is just whatever chart you're using
// x is used here I'm assuming to find f(x), your value
private double GetYOffset(ChartClass chart, double x)
{
double yOffset;
// yOffset = height * (rangeMax - value) / (rangeMax - rangeMin);
return yOffset;
}
In this function I get the position of cursor X with the method GetXOfCursor();
after that I take the series point with this statement:
points = _theChart.Series[i].Points;
After with the last if statement I see the point that matches with the position X of the cursor
and I calculate the mean value of Y of this 2 Point because the point intercepted with the cursor is an interpolation line made by the windows chart control
public string GetPointInterceptedCursorX()
{
string values = string.Empty;
// Far uscire le etichette ai punti intercettati
var xPosCursor = GetXOfCursor();
DataPointCollection points = null;
for (int i = 0; i < _theChart.Series.Count; i++)
{
if (_theChart.Series[i].BorderWidth == ChartConst.THICKNESS_LINE_ENABLED)
{
points = _theChart.Series[i].Points;
break;
}
}
if (points == null) return "No Curve Selected";
for (int i = 0; i < points.Count - 1; i++)
{
if ((xPosCursor > points[i].XValue & xPosCursor < points[i + 1].XValue) | xPosCursor > points[i + 1].XValue & xPosCursor < points[i].XValue)
{
var Yval = (points[i].YValues[0] + points[i + 1].YValues[0]) / 2;
values += $"Y= {Yval} {Environment.NewLine}";
}
}
values += "X=" + " " + String.Format("{0:0.00}", xPosCursor);
return values;
}

Can't get while loop to draw

I'm keeping the drawing of each object in a seperate class and calling on it then in the main Draw Class. Anyway, I need to get this loop right because I'll be modelling a lot off it.
It will draw the first time for me but after that the xCoord doesn't seem to be moving for me or else there's something else wrong with the loop. There's no syntax errors or the like, the program runs, just not how I want it to!
Any help would be much appreciated...
/// <summary>
/// Draws the bottom platform (ground)
/// </summary>
public void DrawBottomPlatform()
{
int xCoord = 0;
int yCoord = (screenHeight / 10) * 9;
int width = screenWidth / 20;
int height = screenHeight / 20;
Rectangle bottomRectangle = new Rectangle(xCoord, yCoord, width, height);
int i = 0;
while (i <= 5)
{
spriteBatch.Draw(grassTexture, bottomRectangle, Color.White);
xCoord += bottomRectangle.Width;
i += 1;
}
}
You never use your updated xCoord, the value is simply ignored once the loops starts. Instead of updating xCoord, move the rectangle at each iteration:
int i = 0;
while (i <= 5)
{
spriteBatch.Draw(grassTexture, bottomRectangle, Color.White);
bottomRectangle.X += bottomRectangle.Width;
i += 1;
}

C# Changing the value of an element in a list of type Rectangle

I'm struggling to set the values of an element at a specific index within a list to a new value.
The list is of type object Rectangle and i get the following error when I try change any of the values of the rectangle in the list e.g.
Property or indexer 'System.Drawing.Rectangle.Bottom' cannot be
assigned to -- it is read only
I've tried converting the list to an array but i still run into the same issue of the values being read-only.
Basically the app takes in a user defined number of rectangles and draws the rectangles with varying widths and heights but along the same baseline. The code im trying to implement needs to take those rectangles and redraw them vertically from the base upwards, while keeping the same number of rectangles and keeping the same outer shape as the previous rectangles created.
Code:
public void RotateRectangles(List<Rectangle> Rectangles, int startPositionX, int userInput, Graphics DrawingSurface)
{
Graphics RectangleGraphics = DrawingSurface;
try
{
// loop in reverse to compare one rectangle to all the other rectangles in the vector
for (int i = Rectangles.Count - 1; i > -1; --i)
{
bool mustChange = true;
for (int t = Rectangles.Count - 1; t > -1; --t)
{
// only compare if the current position in the vector A if different to the position in vector B.
if (i > t)
{
if (mustChange == true)
{
// If the top Y coordinate of RECT at Position i in vector A is bigger than Y coordinate
// at Position t in vector B
if (Rectangles[i].Top >= Rectangles[t].Top)
{
//adjusting points accordingly
Rectangles[i].Left = (Rectangles[t].Left);
Rectangles[t].Bottom = (Rectangles[i].Top);
}
else
{
// If the Y coordinate is not bigger, then we need to stop checking
mustChange = false;
}
}
}
}
}
// loop forward to compare one rectangle to all the other rectangles in the vector
for (int i = 0; i < Rectangles.Count; ++i)
{
bool forwardChange = true;
for (int t = 0; t < Rectangles.Count; ++t)
{
// If the top Y coordinate of RECT at Position i in vector A is bigger than Y coordinate at Position t
// in vector B AND the two rectangales touch
if (i < t && Rectangles[i].Top <= Rectangles[t].Bottom)
{
if (forwardChange == true)
{
// If the top Y coordinate of RECT at Position i in vector A is bigger than Y coordinate at Position t
// in vector B
if (Rectangles[i].Top > Rectangles[t].Top)
{
//adjusting points accordingly
Rectangles[i].Right = (Rectangles[t].Right);
Rectangles[t].Bottom = (Rectangles[i].Top);
}
else
{
// If the Y coordinate is not bigger, then we need to stop checking
forwardChange = false;
// Addjust the Y position of each rectangle so it does not overlap with the first drawing
for (int z = 0; z < Rectangles.Count; ++z)
{
Rectangles[z].Top = (250 - Rectangles[z].Top);
Rectangles[z].Bottom = (250 - Rectangles[z].Bottom);
}
}
}
}
}
}
for (int z = 0; z < Rectangles.Count; ++z)
{
Rectangle DrawRec = myRectangleClass.MyRectangle(Rectangles[z].Left, Rectangles[z].Top, Rectangles[z].Right, Rectangles[z].Bottom);
RectangleGraphics.DrawRectangle(Pen, DrawRec);
ReadWrite.writeOutput(Rectangles[z].Left, Rectangles[z].Top, Rectangles[z].Right, Rectangles[z].Bottom);
}
}
catch (Exception e)
{
}
}
The parts that are giving the errors are:
Rectangles[i].Left = (Rectangles[t].Left);
Rectangles[t].Bottom = (Rectangles[i].Top);
Rectangles[i].Right = (Rectangles[t].Right);
Rectangles[t].Bottom = (Rectangles[i].Top);
Rectangles[z].Top = (250 - Rectangles[z].Top);
Rectangles[z].Bottom = (250 - Rectangles[z].Bottom);
Please can someone help me out or at least direct me in the right direction
The properties Right, Left, Top, Bottom are read-only. You can use the methods Offset and Inflate, and the properties Location, Size, Width and Height, to adjust the position and size of the rectangle, or you can replace an existing rectangle with a newly created one.
e.g.
Rectangle ri = Rectangles[i];
Rectangle rt = Rectangles[t];
Rectangle[i] = new Rectangle( rt.Left, ri.Bottom, rt.Height, rt.Width );
the problem is less the value type of the rectangle, more that the List[] operator returns a new value object.
List<Rectangle> a;
a[4].X += 4; // does the same like the following code:
var r = a[4]
r.X += 4; // will change r, but not a[4]
so you need to store back the rectangle value in the list
Rectangles[i] = new Rectangle(Rectangles[t].Left, Rectangles[i].Top, Rectangles[i].Width, Rectangles[i].Height);
Assign a new rectangle to the wanted index.

Drawing Isometric Tilemaps

I've been working on drawing an isometric map with C# / XNA Game Studio, and while I've gotten pretty far it doesn't look quite right and I was wondering if anybody can help.
Here's the code I have for drawing the map:
public void Draw(SpriteBatch theBatch, int drawX, int drawY)
{
if ((drawY % 2 == 0))
theBatch.Draw(tileTexture, new Rectangle((drawX * width), (drawY * length / 2), width, length), Color.White);
else
theBatch.Draw(tileTexture, new Rectangle(((drawX * width) + (width / 2)), (drawY * length / 2), width, length), Color.White);
}
The code within this method acts as if it were inside a nested for loop, drawing left to right, top to bottom. When the y-value is odd, the row is shifted over to fit, however it looks a bit off.
This is the produced output for an 8x5 map:
As you can see, it doesn't quite look right, and I'm not sure if its an issue with the math in my code, or if it has to do with the order everything is being drawn in. I'm very new to C# and working with sprites, so any help would be greatly appreciated.
Because it might be helpful, here is the other relevant parts of code which draw the map.
The entire Tile Class:
public class Tile
{
// Dimension variables
int height;
int width;
int length;
String type;
Texture2D tileTexture;
Vector2 coordinates;
///
/// Tile Constructor
///
public Tile(ContentManager theContent, String theType, Vector2 theCoordinates)
{
width = 68;
length = 46;
type = theType;
coordinates = theCoordinates;
// Sets the right texture to the texture ref
if (theType == "grass")
tileTexture = theContent.Load<Texture2D>(#"Tiles\iso_grass");
}
///
/// Draws the tile at the given location
///
public void Draw(SpriteBatch theBatch, int drawX, int drawY)
{
if ((drawY % 2 == 0))
theBatch.Draw(tileTexture, new Rectangle((drawX * width), (drawY * length / 2), width, length), Color.White);
else
theBatch.Draw(tileTexture, new Rectangle(((drawX * width) + (width / 2)), (drawY * length / 2), width, length), Color.White);
}
}
The TileRow class, which holds one row of tiles.
public class TileRow
{
public List<Tile> Row = new List<Tile>();
public int rowLength;
public TileRow(int theLength, int yIndex, ContentManager theContent)
{
rowLength = theLength;
Tile thisTile;
// Here the tiles are created and added to the row
for (int x = 0; x < rowLength; x++)
{
thisTile = new Tile(theContent, "grass", new Vector2(x, yIndex));
Row.Add(thisTile);
}
}
///
/// Draw -- invokes the draw method of each tile in the row
///
public void DrawRow(SpriteBatch theBatch, int currentY)
{
for (int x = 0; x < rowLength; x++)
{
Row[x].Draw(theBatch, currentY, x);
}
}
}
}
and the MapStruct class, which holds all the rows
public class MapStruct
{
public List<TileRow> allRows = new List<TileRow>();
int depth;
// Constructor
public MapStruct(ContentManager theContent, int theDepth, int rowLength)
{
depth = theDepth;
TileRow thisRow;
// Here we make a row of tiles for each level of depth
for (int y = 0; y < depth; y++)
{
thisRow = new TileRow(rowLength, depth, theContent);
allRows.Add(thisRow);
}
}
///
/// Draw - this method invokes the draw method in each tile row, which then draws each tile
///
public void DrawMap(SpriteBatch theBatch)
{
for (int y = 0; y < depth; y++)
{
allRows[y].DrawRow(theBatch, y);
}
}
}
Any help on how I could fix this issue, as well as advice on how I could improve my code would be greatly appreciated!
Looks like your loop adds a little to much to the Y on each row.
I found this variable in your Tile function.
length = 46;
I havent checked, but I believe "length" is the height of the tile? if so, try ajusting it a bit. Perhaps, you've forgotten to minus the height of the tile. So if the side of the tile is like 6 pixels, then the length for offset pr. row is only 40.
Also remember to plot from top and down, since the tiles nearest camera has to be plotted last, to make the depth illusion.
I beleive BerggreenDK is right, but your comment make it seem you misunderstood his answer. Your tiles need to be drawn at an Y offset that only includes the green surface areas "screen height". So, draw the full tile size, but offset the rows with length - 5 (Where 10 is the estimated offset and 5 is half that, as each row should be offset by only half the distance).
theBatch.Draw(tileTexture, new Rectangle((drawX * width), (drawY * (length) / 2), width, length - 5), Color.White);
...if I got the parameters right.
Also, the rows need to be drawn from back to front, but they seem to be drawn from left to right? I don't know XNA, so can't show code to fix that...when I look closely some tiles in a row "further away" is over-drawn by a tile "closer". I don't know XNA and don't get how the drawing order is determined, so can't offer a fix for that...

Categories

Resources