I am writing an app which allow user to draw on a touch screen display. I am currently using the method below and it work very well. This method is producing a “high resolution image” since for almost every single pixel a line is drawn (e.g. 100, 100 -> 102, 103).
Here is my question. I’d like user to draw a “low resolution picture” (big pixels board) where you can intentionally see pixels of 50×50 (e.g. 100, 100 -> 150, 150). Does anybody have an idea on how to accomplish that? I am using Silverlight for Windows Phone. I was thinking about building a big grid of 50×50 pixels, but there might be too many controls.
void FingerMove(object sender, MouseEventArgs e)
{
if (this.IsDrawing)
{
this.DestinationPoint = e.GetPosition(paint);
Line line = new Line
{
Stroke = this.Color,
X1 = this.DestinationPoint.X,
Y1 = this.DestinationPoint.Y,
X2 = this.OriginPoint.X,
Y2 = this.OriginPoint.Y,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round,
StrokeThickness = 15,
Opacity = 1,
};
Debug.WriteLine(string.Join(",", line.X1, line.Y1, line.X2, line.Y2));
paint.Children.Add(line);
}
this.OriginPoint = this.DestinationPoint;
}
#Amr has the right idea. I'll give you this code with the caveat that I haven't tested it at all. I took the line segment intersection algorithm from here.
First, you need to set up a list of Rectangles and add them to the canvas that are your "pixels":
private List<Rectangle> _rects = new List<Rectangle>();
private void GenerateRects()
{
int width = 300; // or whatever dimensions...
int height = 300;
int gridSize = 50;
for (int x = 0; x < width; x += gridSize)
{
for (int y = 0; y < height; y += gridSize)
{
var rect = new Rectangle
{
Opacity = 0,
Width = Math.Min(gridSize, width - x),
Height = Math.Min(gridSize, height - y),
};
Canvas.SetLeft(rect, x);
Canvas.SetTop(rect, y);
_rects.Add(rect);
this.paint.Children.Add(rect);
}
}
}
We'll need these helper methods:
class LineSegment
{
public double X1 { get; set; }
public double X2 { get; set; }
public double Y1 { get; set; }
public double Y2 { get; set; }
}
private static bool SegmentsIntersect(LineSegment A, LineSegment B)
{
double x1 = A.X1, x2 = A.X2, x3 = B.X1, x4 = B.X2;
double y1 = A.Y1, y2 = A.Y2, y3 = B.Y1, y4 = B.Y2;
double denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (denominator == 0)
return false;
double ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
double ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
return (ua > 0 && ua < 1 && ub > 0 && ub < 1);
}
private static bool RectIntersectsLine(Rect A, LineSegment B)
{
return (SegmentsIntersect(B, new LineSegment { X1 = A.X, Y1 = A.Y, X2 = A.X, Y2 = A.Y + A.Height }) ||
SegmentsIntersect(B, new LineSegment { X1 = A.X, Y1 = A.Y + A.Height, X2 = A.X + A.Width, Y2 = A.Y + A.Height }) ||
SegmentsIntersect(B, new LineSegment { X1 = A.X + A.Width, Y1 = A.Y + A.Height, X2 = A.X + A.Width, Y2 = A.Y }) ||
SegmentsIntersect(B, new LineSegment { X1 = A.X + A.Width, Y1 = A.Y, X2 = A.X, Y2 = A.Y }) ||
RectContainsPoint(A, new Point(B.X1, B.Y1)) ||
RectContainsPoint(A, new Point(B.X2, B.Y2)));
}
private static bool RectContainsPoint(Rect A, Point B)
{
return (B.X > A.X && B.X < A.X + A.Width && B.Y > A.Y && B.Y < A.Y + A.Height);
}
Then, in the FingerMove function, we loop through each Rectangle to see if it intersects. If it does, we change its color:
void FingerMove(object sender, MouseEventArgs e)
{
if (this.IsDrawing)
{
this.DestinationPoint = e.GetPosition(paint);
LineSegment line = new LineSegment
{
X1 = this.DestinationPoint.X,
Y1 = this.DestinationPoint.Y,
X2 = this.OriginPoint.X,
Y2 = this.OriginPoint.Y
};
foreach (var rect in _rects)
{
var x = Canvas.GetLeft(rect);
var y = Canvas.GetTop(rect);
if (RectIntersectsLine(new Rect(x, y, rect.Width, rect.Height) , line))
{
rect.Opacity = 1;
rect.Fill = Color;
}
}
}
this.OriginPoint = this.DestinationPoint;
}
If you simply want to make the line thicker, just experiment with possible values of StrokeThickness untill you get the desired effect.
If you want to manually draw the line by filling up large areas of the screen say (50x50) rectangles, you could do the follwing:
divide the screen into 50x50 rectangles
check which rectangles are intersected by the line drawn by the user
only draw the rectangles from step 2
This would give you the 'snap to grid' line that you want.
Related
When i call my funtion with a startingAngle=0 it produce a good shape with the correct size.
Example:
var points = GetPolygonVertices(sides:4, radius:5, center:(5, 5), startingAngle:0), produces:
points[0] = {X = 10 Y = 5}
points[1] = {X = 5 Y = 0}
points[2] = {X = 0 Y = 5}
points[3] = {X = 5 Y = 10}
As observed the side length is 10px, which is correct, but produce a rotated square at 45º from human eye prespective.
To fix this i added a switch/case to offset the startAngle so it will put the square at correct angle for human eye, by rotating 45º. The rotation works, but the shape is no longer a square of 10x10px, instead i lose 1 to 2px from sides:
[0] = {X = 9 Y = 1}
[1] = {X = 1 Y = 1}
[2] = {X = 1 Y = 9}
[3] = {X = 9 Y = 9}
and become worse as radius grow, for example with radius=10:
[0] = {X = 17 Y = 3}
[1] = {X = 3 Y = 3}
[2] = {X = 3 Y = 17}
[3] = {X = 17 Y = 17}
I tried with both floor and ceil instead of round, but it always end in lose 1 or 2px...
Is there a way to improve the function to keep the shape size equal no matter the number of sides and rotation angle?
My function:
public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0)
{
if (sides < 3)
throw new ArgumentException("Polygons can't have less than 3 sides...", nameof(sides));
// Fix rotation
switch (sides)
{
case 3:
startingAngle += 90;
break;
case 4:
startingAngle += 45;
break;
case 5:
startingAngle += 22.5;
break;
}
var points = new Point[sides];
var step = 360.0 / sides;
int i = 0;
for (var angle = startingAngle; angle < startingAngle + 360.0; angle += step) //go in a circle
{
if (i == sides) break; // Fix floating problem
double radians = angle * Math.PI / 180.0;
points[i++] = new(
(int) Math.Round(Math.Cos(radians) * radius + center.X),
(int) Math.Round(Math.Sin(-radians) * radius + center.Y)
);
}
return points;
}
EDIT: I updated the function to get rid of the switch condition and product shapes in correct orientation for human eye when angle is not given. Still it suffer from same "problem"
public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0, bool flipHorizontally = false, bool flipVertically = false)
{
if (sides < 3)
throw new ArgumentException("Polygons can't have less than 3 sides...", nameof(sides));
var vertices = new Point[sides];
double deg = 360.0 / sides;//calculate the rotation angle
var rad = Math.PI / 180.0;
var x0 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + startingAngle) * rad);
var y0 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + startingAngle) * rad);
var x1 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + deg + startingAngle) * rad);
var y1 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + deg + startingAngle) * rad);
vertices[0] = new(
(int) Math.Round(x0),
(int) Math.Round(y0)
);
vertices[1] = new(
(int) Math.Round(x1),
(int) Math.Round(y1)
);
for (int i = 0; i < sides - 2; i++)
{
double dsinrot = Math.Sin((deg * (i + 1)) * rad);
double dcosrot = Math.Cos((deg * (i + 1)) * rad);
vertices[i + 2] = new(
(int)Math.Round(center.X + dcosrot * (x1 - center.X) - dsinrot * (y1 - center.Y)),
(int)Math.Round(center.Y + dsinrot * (x1 - center.X) + dcosrot * (y1 - center.Y))
);
}
if (flipHorizontally)
{
var startX = center.X - radius;
var endX = center.X + radius;
for (int i = 0; i < sides; i++)
{
vertices[i].X = endX - (vertices[i].X - startX);
}
}
if (flipVertically)
{
var startY = center.Y - radius;
var endY = center.Y + radius;
for (int i = 0; i < sides; i++)
{
vertices[i].Y = endY - (vertices[i].Y - startY);
}
}
return vertices;
}
EDIT 2: From Tim Roberts anwser here the functions to calculate side length from radius and radius from side length, this solve my problem. Thanks!
public static double CalculatePolygonSideLengthFromRadius(double radius, int sides)
{
return 2 * radius * Math.Sin(Math.PI / sides);
}
public static double CalculatePolygonVerticalLengthFromRadius(double radius, int sides)
{
return radius * Math.Cos(Math.PI / sides);
}
public static double CalculatePolygonRadiusFromSideLength(double length, int sides)
{
var theta = 360.0 / sides;
return length / (2 * Math.Cos((90 - theta / 2) * Math.PI / 180.0));
}
Your problem is one of mathematics. You said "As observed, the side length is 10px". It very definitely is not 10px. The distance from (10,5) to (5,0) is sqrt(5*5 + 5*5), which is 7.07. That's exactly what we expect for a square that is inscribed in a circle of radius 5: 5 x sqrt(2).
And that's what the other squares are as well.
FOLLOWUP
As an added bonus, here is a function that returns the radius of the circle that circumscribes a regular polygon with N sides of length L:
import math
def rad(length,nsides):
theta = 360/nsides
r = length / (2 * math.cos( (90-theta/2) * math.pi / 180))
return r
for s in range(3,9):
print(s, rad(10,s))
I have a problem. In my MainActivity I call fragments when you click a button. One of the fragments is a SkiaSharp canvas with a TriangleGrid. When you click on a Triangle, it changes the color of that Triangle. Now when I go the SkiaSharp fragment the first time, everything loads correctly like I want, but when I go to a different page and then go back to the SkiaSharp canvas, The whole canvas is blank. The triangle still get's filled when you tap it, but the TriangleGrid is gone. Also the Triangles that were previously clicked, except for the last one. Here are the most important parts of my code. This is the FULL MainActivity:
public class MainActivity : FragmentActivity
{
Dictionary<string, TriangleRegistryObject> TriangleRegistry = new Dictionary<string, TriangleRegistryObject>();
//ALL FRAGMENTS
private Android.Support.V4.App.Fragment CurrentFragment;
private int CurrentFragmentNum;
private int PreviousFragmentNum;
private Shape_Selection ShapeSelectionFragment;
private Shape_Formation ShapeFormationFragment;
private string Direction;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
ActionBar.SetCustomView(Resource.Layout.setup_nav_bar);
ActionBar.SetDisplayShowCustomEnabled(true);
((Toolbar)ActionBar.CustomView.Parent).SetContentInsetsAbsolute(0, 0);
SetContentView(Resource.Layout.activity_main);
Button btnBack = FindViewById<Button>(Resource.Id.btnBack);
Button btnNext = FindViewById<Button>(Resource.Id.btnNext);
btnBack.Click += btnBack_Click;
btnNext.Click += btnNext_Click;
ShapeSelectionFragment = new Shape_Selection();
ShapeFormationFragment = new Shape_Formation();
var trans = SupportFragmentManager.BeginTransaction();
trans.Add(Resource.Id.fragmentContainer, ShapeFormationFragment, "Shape_Formation");
trans.Hide(ShapeFormationFragment);
trans.Add(Resource.Id.fragmentContainer, ShapeSelectionFragment, "Shape_Selection");
//trans.Hide(ShapeSelectionFragment);
trans.Commit();
CurrentFragmentNum = 1;
Direction = "Startup";
ShowFragment(CurrentFragmentNum, 1, Direction);
}
private void ShowFragment (int CurrentFragmentNum, int PreviousFragmentNum, string Direction)
{
Android.Support.V4.App.Fragment fragment = null;
Android.Support.V4.App.Fragment PreviousFragment = null;
switch (CurrentFragmentNum)
{
case 1:
fragment = ShapeSelectionFragment;
break;
case 2:
fragment = ShapeFormationFragment;
break;
}
switch (PreviousFragmentNum)
{
case 1:
PreviousFragment = ShapeSelectionFragment;
break;
case 2:
PreviousFragment = ShapeFormationFragment;
break;
}
var trans = SupportFragmentManager.BeginTransaction();
switch (Direction)
{
case "Forwards":
trans.SetCustomAnimations(Resource.Animation.slide_in_right, Resource.Animation.slide_out_left);
break;
case "Backwards":
trans.SetCustomAnimations(Resource.Animation.slide_in_left, Resource.Animation.slide_out_right);
break;
}
trans.Replace(Resource.Id.fragmentContainer, fragment);
trans.Hide(PreviousFragment);
trans.Show(fragment);
trans.AddToBackStack(null);
trans.Commit();
CurrentFragment = fragment;
if(CurrentFragment == ShapeSelectionFragment)
{
CurrentFragmentNum = 1;
}
else if (CurrentFragment == ShapeFormationFragment)
{
CurrentFragmentNum = 2;
}
}
public void btnBack_Click(object sender, EventArgs e)
{
if (CurrentFragmentNum > 1)
{
PreviousFragmentNum = CurrentFragmentNum;
CurrentFragmentNum -= 1;
Direction = "Backwards";
ShowFragment(CurrentFragmentNum, PreviousFragmentNum, Direction);
}
}
public void btnNext_Click(object sender, EventArgs e)
{
if (CurrentFragmentNum < 2)
{
PreviousFragmentNum = CurrentFragmentNum;
CurrentFragmentNum += 1;
Direction = "Forwards";
ShowFragment(CurrentFragmentNum, PreviousFragmentNum, Direction);
}
}
}
And this is FULL the Shape_Formation.cs:
public class Shape_Formation : Android.Support.V4.App.Fragment, IOnTouchListener
{
private SKCanvasView skiaView;
private SKPaint TriangleFillColor;
private string Action { get; set; }
Dictionary<string, TriangleRegistryObject> TriangleRegistry = new Dictionary<string, TriangleRegistryObject>();
private int UnscaledWidth;
private int UnscaledHeight;
private int TrianglesPerUpperRow;
private int TrianglesPerLowerRow;
private int TrianglesVisiblePerRow = 4;
private int CurrentRow = 0;
private float TriangleWidth;
private float TriangleRowHeight;
//ALL SHAPE PATHS
private SKPath TriangleGridPath = new SKPath();
private SKPath TrianglePath = new SKPath();
//================//
// PAINT VARIANTS //
//================//
SKPaint TriangleGridPaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.White,
StrokeWidth = 3
};
SKPaint SelectTrianglePaint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColors.Blue
};
SKPaint UnselectTrianglePaint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColor.Parse("#0e2a56")
};
SKPaint BackgroundPaint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColor.Parse("#0e2a56")
};
SKPaint FocusedTrianglePaint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColors.LightGray
};
//=======================//
// END OF PAINT VARIANTS //
//=======================//
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.Inflate(Resource.Layout.setup_shape_formation, container, false);
skiaView = view.FindViewById<SKCanvasView>(Resource.Id.skiaView);
skiaView.SetOnTouchListener(this);
skiaView.PaintSurface += PaintSurface;
return view;
}
private void PaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
//==============//
// CANVAS SETUP //
//==============//
SKCanvas canvas = e.Surface.Canvas;
//SOME VARIABLES OF SCREEN SIZE
UnscaledWidth = e.Info.Width;
UnscaledHeight = e.Info.Height;
//=====================//
// END OF CANVAS SETUP //
//=====================//
if (TriangleFillColor != null)
{
canvas.DrawPath(TrianglePath, TriangleFillColor);
canvas.DrawPath(TrianglePath, TriangleGridPaint);
}
//====================//
// DRAW TRIANGLE GRID //
//====================//
int TriangleCount = 0;
TriangleWidth = UnscaledWidth / TrianglesVisiblePerRow;
TriangleRowHeight = (float)Math.Sqrt(Math.Pow(TriangleWidth, 2) - (Math.Pow((UnscaledWidth / (TrianglesVisiblePerRow * 2)), 2)));
CurrentRow = 1;
float X_Start_CooridnateUpperRow = 0;
float Y_Start_CooridnateUpperRow = 0;
float X_Start_CooridnateLowerRow = -(TriangleWidth / 2);
float Y_Start_CooridnateLowerRow = TriangleRowHeight;
SKPaint TriangleBorderPaint;
//DRAW MULTIPLE ROWS
while (Y_Start_CooridnateUpperRow <= UnscaledHeight)
{
TrianglesPerUpperRow = 5;
TrianglesPerLowerRow = 5;
//SET THE TRIANGLE COUNTER FOR BOTH ROWS
int TriangleUpperCountOnRow = 0;
int TriangleLowerCountOnRow = 0;
//DRAW THE UPPER ROW OF TRIANGLES
while (TriangleUpperCountOnRow < TrianglesPerUpperRow)
{
float x1 = X_Start_CooridnateUpperRow;
float y1 = Y_Start_CooridnateUpperRow;
float x2 = X_Start_CooridnateUpperRow + TriangleWidth;
float y2 = y1;
float x3 = X_Start_CooridnateUpperRow + TriangleWidth / 2;
float y3 = TriangleRowHeight * (CurrentRow);
TriangleGridPath = new SKPath();
TriangleGridPath.MoveTo(x1, y1);
TriangleGridPath.LineTo(x2, y2);
TriangleGridPath.LineTo(x3, y3);
TriangleGridPath.LineTo(x1, y1);
TriangleGridPath.Close();
TriangleRegistryObject value;
TriangleRegistry.TryGetValue(TriangleCount.ToString(), out value);
if (value == null)
{
if (x1 > UnscaledWidth ||
x2 > UnscaledWidth ||
x3 > UnscaledWidth ||
x1 < 0 ||
x2 < 0 ||
x3 < 0 ||
y1 > UnscaledHeight ||
y2 > UnscaledHeight ||
y3 > UnscaledHeight)
{
TriangleBorderPaint = BackgroundPaint;
TriangleRegistry[TriangleCount.ToString()] = new TriangleRegistryObject { x1 = x1, y1 = y1, x2 = x2, y2 = y2, x3 = x3, y3 = y3, Selected = false, Visible = false };
}
else
{
TriangleBorderPaint = TriangleGridPaint;
TriangleRegistry[TriangleCount.ToString()] = new TriangleRegistryObject { x1 = x1, y1 = y1, x2 = x2, y2 = y2, x3 = x3, y3 = y3, Selected = false, Visible = true };
}
canvas.DrawPath(TriangleGridPath, TriangleBorderPaint);
}
TriangleCount += 1;
TriangleUpperCountOnRow += 1;
X_Start_CooridnateUpperRow += TriangleWidth;
}
//DRAW THE LOWER ROW OF TRIANGLES
while (TriangleLowerCountOnRow < TrianglesPerLowerRow)
{
float x1 = X_Start_CooridnateLowerRow;
float y1 = Y_Start_CooridnateLowerRow;
float x2 = X_Start_CooridnateLowerRow + TriangleWidth;
float y2 = y1;
float x3 = X_Start_CooridnateLowerRow + (TriangleWidth / 2);
float y3 = TriangleRowHeight * (CurrentRow - 1);
TriangleGridPath = new SKPath();
TriangleGridPath.MoveTo(x1, y1);
TriangleGridPath.LineTo(x2, y2);
TriangleGridPath.LineTo(x3, y3);
TriangleGridPath.LineTo(x1, y1);
TriangleGridPath.Close();
TriangleRegistryObject value;
TriangleRegistry.TryGetValue(TriangleCount.ToString(), out value);
if (value == null)
{
if (x1 > UnscaledWidth ||
x2 > UnscaledWidth ||
x3 > UnscaledWidth ||
x1 < 0 ||
x2 < 0 ||
x3 < 0 ||
y1 > UnscaledHeight ||
y2 > UnscaledHeight ||
y3 > UnscaledHeight)
{
TriangleBorderPaint = BackgroundPaint;
TriangleRegistry[TriangleCount.ToString()] = new TriangleRegistryObject { x1 = x1, y1 = y1, x2 = x2, y2 = y2, x3 = x3, y3 = y3, Selected = false, Focused = false, Visible = false };
}
else
{
TriangleBorderPaint = TriangleGridPaint;
TriangleRegistry[TriangleCount.ToString()] = new TriangleRegistryObject { x1 = x1, y1 = y1, x2 = x2, y2 = y2, x3 = x3, y3 = y3, Selected = false, Focused = false, Visible = true };
}
canvas.DrawPath(TriangleGridPath, TriangleBorderPaint);
}
TriangleCount += 1;
TriangleLowerCountOnRow += 1;
X_Start_CooridnateLowerRow += TriangleWidth;
}
CurrentRow += 1;
if ((CurrentRow - 1) % 2 != 0)
{
X_Start_CooridnateUpperRow = -(TriangleWidth / 2);
X_Start_CooridnateLowerRow = 0;
}
else
{
X_Start_CooridnateUpperRow = 0;
X_Start_CooridnateLowerRow = -(TriangleWidth / 2);
}
Y_Start_CooridnateUpperRow += TriangleRowHeight;
Y_Start_CooridnateLowerRow += TriangleRowHeight;
}
//==============================//
// END OF DRAWING TRIANGLE GRID //
//==============================//
}
public override void OnDestroy()
{
skiaView.PaintSurface -= PaintSurface;
base.OnDestroy();
}
private void DrawTrianglePath(double x1, double y1, double x2, double y2, double x3, double y3, string key, string Action)
{
bool ChangedState = true;
var Triangle = TriangleRegistry[key];
if (Triangle.Visible == true)
{
if (Triangle.Selected == false)
{
if (Action == "Selected")
{
Triangle.Selected = true;
TriangleRegistry[key] = Triangle;
TriangleFillColor = SelectTrianglePaint;
}
else if (Action == "Focused")
{
if (Triangle.Focused == false)
{
Triangle.Focused = true;
TriangleRegistry[key] = Triangle;
TriangleFillColor = FocusedTrianglePaint;
}
}
}
else
{
Triangle.Selected = false;
TriangleFillColor = UnselectTrianglePaint;
}
if (ChangedState == true)
{
TrianglePath = new SKPath();
TrianglePath.MoveTo((float)Triangle.x1, (float)Triangle.y1);
TrianglePath.LineTo((float)Triangle.x2, (float)Triangle.y2);
TrianglePath.LineTo((float)Triangle.x3, (float)Triangle.y3);
TrianglePath.LineTo((float)Triangle.x1, (float)Triangle.y1);
TrianglePath.Close();
skiaView.Invalidate();
}
}
}
public bool OnTouch(View v, MotionEvent e)
{
if (e.Action == MotionEventActions.Down)
{
var X_Clicked = e.GetX();
var Y_Clicked = e.GetY();
bool TriangleFound = false;
while (TriangleFound == false)
{
foreach (KeyValuePair<string, TriangleRegistryObject> row in TriangleRegistry.ToList())
{
if (isInside(row.Value.x1, row.Value.y1, row.Value.x2, row.Value.y2, row.Value.x3, row.Value.y3, X_Clicked, Y_Clicked))
{
TriangleFound = true;
Action = "Selected";
DrawTrianglePath(row.Value.x1, row.Value.y1, row.Value.x2, row.Value.y2, row.Value.x3, row.Value.y3, row.Key, Action);
}
}
}
}
return true;
}
static double area(double x1, double y1, double x2, double y2, double x3, double y3)
{
return Math.Abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2.0);
}
static bool isInside(double x1, double y1, double x2, double y2, double x3, double y3, double x, double y)
{
double A = area(x1, y1, x2, y2, x3, y3);
double A1 = area(x, y, x2, y2, x3, y3);
double A2 = area(x1, y1, x, y, x3, y3);
/* Calculate area of triangle PAB */
double A3 = area(x1, y1, x2, y2, x, y);
/* Check if sum of A1, A2 and A3 is same as A */
return (A == A1 + A2 + A3);
}
}
Can someone help me with this problem!?
you could see these lines of code in your ShowFragment:
trans.Replace(Resource.Id.fragmentContainer, fragment);
trans.Hide(PreviousFragment);
trans.Show(fragment);
trans.AddToBackStack(null);
trans.Commit();
you use replace,it will destory the fragment you added it in Resource.Id.fragmentContainer before,so it couldn't store the canvas,here you only want to show and hide the fragment you added before,you could just change like this(This will not destroy the fragment but hide and show):
trans.Hide(PreviousFragment);
trans.Show(fragment);
trans.Commit();
I am making an automated floor plan generation desktop app. In this, at first I draw the polygon on points, using this method
public void DrawPolygonPointF(PaintEventArgs e) {
// Create pen.
Pen blackPen = new Pen(Color.Black, 3);
// Create points that define polygon.
PointF point1 = new PointF(50.0F, 50.0F);
PointF point2 = new PointF(100.0F, 25.0F);
PointF point3 = new PointF(200.0F, 5.0F);
PointF point4 = new PointF(250.0F, 50.0F);
PointF point5 = new PointF(300.0F, 100.0F);
PointF point6 = new PointF(350.0F, 200.0F);
PointF point7 = new PointF(250.0F, 250.0F);
PointF[] curvePoints =
{
point1,
point2,
point3,
point4,
point5,
point6,
point7
};
// Draw polygon curve to screen.
e.Graphics.DrawPolygon(blackPen, curvePoints);
}
NOTE: These points are not actual points, they are for only demo purpose. I am reading the points from a text file.
Now I need to generate a special type of grid.
In generating the grid the first step is detect corners and extend the corner lines.
How do I detect the corners of a polygon so can i move to next step of generating grid?
Corners are marked. I need to extend corner marked with black on horizontally left side and other one is to extend on right side till its touch the line.
A screenshot is attached.
Thanks in advance
In my understanding you are trying to extend edges, not corners.
the procedure could be:
Enumerate edges (each 2 adjacent points define an edge)
For each edge find if it vertical or horizontal (abs(x1-x2) > abs(y1-y2))
Find if edge can be extended, the horizontal (left/right) and the vertical (up/bottom)
bool CheckHorizontalExtensibilityToRight(Point[] curvePoints, Point corner)
{
return curvePoints.Any(cp=>cp.Y < corner.Y && cp.X < corner.X);
}
Try this example and see if you can adapt it to solve your problem...
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication
{
public partial class Form1 : Form
{
private struct LineSegment
{
private PointF _a, _b;
public PointF A { get { return _a; } }
public PointF B { get { return _b; } }
public LineSegment(PointF a, PointF b)
{
_a = a; _b = b;
}
public float GetLengthSquared()
{
var dx = _a.X - _b.X;
var dy = _a.Y - _b.Y;
return dx * dx + dy * dy;
}
public bool RectContains(PointF a)
{
var x = a.X;
var y = a.Y;
var x1 = _a.X;
var y1 = _a.Y;
var x2 = _b.X;
var y2 = _b.Y;
return (x1 < x2 ? x1 <= x && x2 >= x : x2 <= x && x1 >= x) && (y1 < y2 ? y1 <= y && y2 >= y : y2 <= y && y1 >= y);
}
public bool ExtendToIntersectWith(LineSegment b)
{
var x1 = _a.X;
var y1 = _a.Y;
var x2 = _b.X;
var y2 = _b.Y;
var x3 = b._a.X;
var y3 = b._a.Y;
var x4 = b._b.X;
var y4 = b._b.Y;
var a1 = y2 - y1;
var b1 = x1 - x2;
var c1 = x1 * y2 - x2 * y1;
var a2 = y4 - y3;
var b2 = x3 - x4;
var c2 = x3 * y4 - x4 * y3;
var d = a1 * b2 - b1 * a2;
if (d == 0)
return false;
var x = (c1 * b2 - b1 * c2) / d;
var y = (a1 * c2 - c1 * a2) / d;
var p = new PointF(x, y);
if (b.RectContains(p) && !RectContains(p))
{
if (new LineSegment(_a, p).GetLengthSquared() < new LineSegment(_b, p).GetLengthSquared())
_a = p;
else
_b = p;
return true;
}
return false;
}
}
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
PointF[] curvePoints =
{
/*
new PointF(50.0F, 50.0F),
new PointF(100.0F, 25.0F),
new PointF(200.0F, 5.0F),
new PointF(250.0F, 50.0F),
new PointF(300.0F, 100.0F),
new PointF(350.0F, 200.0F),
new PointF(250.0F, 250.0F)
*/
new PointF(30F, 10F),
new PointF(60F, 10F),
new PointF(60F, 20F),
new PointF(90F, 20F),
new PointF(90F, 60F),
new PointF(10F, 60F),
new PointF(10F, 40F),
new PointF(30F, 40F),
};
int n = curvePoints.Length;
LineSegment[] lineSegments = new LineSegment[n];
int i = 0;
for (; i < n - 1; ++i)
lineSegments[i] = new LineSegment(curvePoints[i], curvePoints[i + 1]);
lineSegments[i] = new LineSegment(curvePoints[i], curvePoints[0]);
for (i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
lineSegments[i].ExtendToIntersectWith(lineSegments[j]);
for (i = 0; i < n; ++i)
{
var lineSegment = lineSegments[i];
e.Graphics.DrawLine(Pens.Black, lineSegment.A, lineSegment.B);
}
//e.Graphics.DrawPolygon(Pens.Black, curvePoints);
}
}
}
Hi guys assist me with checking if rectangle B is wholly contained in rectangle A, here is a sample code to work with:
Class Rectangle
Public class Rectangle
{
Public int X1 { get; set; }
Public int X2 { get; set; }
Public int Y1 { get; set; }
Public int Y2 { get; set; }
public bool IsWhollyContained(Rectangle otherRectangle)
{
//Write your code here
}
}
Main Method
Public static void main(string[] args)
{
Rectangle A = new Rectangle { X1 = 2, Y1 = 1, X2 = 4, Y2 = 4}};
Rectangle B = new Rectangle { X1 = 1, Y1 = 6, X2 = 5, Y2 = 1}};
bool isContained = B.IsWhollyContained(A);
}
The task is to complete method IsWhollyContained. Please assist if you know the answer, the language used is C#. Thanks guys.
sort the rectangle's points to be sure that you're getting the right bound of rectangle than compare the points, try this:
public bool IsWhollyContained(Rectangle otherRectangle)
{
int rec1Xb = X1 > X2 ? X1 : X2;
int rec1Xs = X1 > X2 ? X2 : X1;
int rec1Yb = Y1 > Y2 ? Y1 : Y2;
int rec1Ys = Y1 > Y2 ? Y2 : Y1;
int rec2Xb = otherRectangle.X1 < otherRectangle.X2 ? otherRectangle.X1 : otherRectangle.X2;
int rec2Xs = otherRectangle.X1 < otherRectangle.X2 ? otherRectangle.X2 : otherRectangle.X1;
int rec2Yb = otherRectangle.Y1 < otherRectangle.Y2 ? otherRectangle.Y1 : otherRectangle.Y2;
int rec2Ys = otherRectangle.Y1 < otherRectangle.Y2 ? otherRectangle.Y2 : otherRectangle.Y1;
return rec1Xs >= rec2Xs &&
rec1Xb <= rec2Xb &&
rec1Ys >= rec2Ys &&
rec1Yb <= rec2Yb;
}
I am assuming that X1, X2, Y1 and Y2 are the coordinates for the lower left and upper right corners of your rectangle.
If another rectangle is supposed to be located inside the first one, the following conditions must be fulfilled:
rectangle1.X1 < rectangle2.X1
rectangle1.Y1 < rectangle2.Y1
rectangle1.X2 > rectangle2.X2
rectangle1.Y2 > rectangle2.Y2
But you don't need to store all those coordinates in your rectangle. System.Drawing has something called Point, you can assign an X- and a Y-Coordinate to that. But if you're already including System.Drawing you can just go with what Hazem Abdullah suggested.
You may use the following code if you are using the System.Drawing Rectangle
Rectangle rect1, rect2;
// initialize them
if(rect1.Contains(rect2))
{
// do...
}
Here is the link 1
If you are using your own rectangle you may check if all the corner points from the first rectangle belong to the second one.
bool PointInsideRectangle(Point pt, double y0, double x0, double y1, double x1)
{
if (x1 < x0)
{
return pt.X < x0 && pt.X > x1 && pt.Y < y0 && pt.Y > y1;
}
// it crosses the date line
return (pt.X < x0 || pt.X > x1) && pt.Y < y0 && pt.Y > y1;
}
Or you need to only check the x0,y0 from the first rectangle to compare with the x0,y0 from the second rectangle, and with, height from the first rectangle and compare it with the second one.
I'm trying to display GPS coordinates in a canvas.
I have 400 towns in France + USA. I just want to display them in a canvas.
For each town I have its latitude and longitude (taken with google api : https://developers.google.com/maps/documentation/geocoding/ ).
I've managed to have a first result using this thread : Converting from longitude\latitude to Cartesian coordinates
Here is my result :
My code is the following (assuming "_liste" contains a list of Towns) :
private void Button_visualise_Click(object sender, RoutedEventArgs e)
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
List<Town> _list = LibDAO.Towns_DAO.GetAllTown().Where(u=>u.IdPays == 2 || u.IdPays == 1).ToList<Town>();
this.my_canvas.Children.Clear();
double min_x = 0;
double max_x = 0;
double min_y = 0;
double max_y = 0;
for (int i = 0; i < _list .Count; i++)
{
Ellipse ell = new Ellipse() { Width = 30, Height = 30, Fill = Brushes.Blue };
Point p = ToCanvas(_list [i].Latitude, _list [i].Longitude);
Canvas.SetLeft(ell, p.X);
Canvas.SetTop(ell, p.Y);
if (p.X < min_x) min_x = p.X;
if (p.X > max_x) max_x = p.X;
if (p.Y < min_y) min_y = p.Y;
if (p.Y > max_y) max_y = p.Y;
this.my_canvas.Children.Add(ell);
}
SetCoordinateSystem(this.my_canvas, min_x, max_x, min_y, max_y);
watch.Stop();
}
public static Canvas SetCoordinateSystem(Canvas canvas, Double xMin, Double xMax, Double yMin, Double yMax)
{
var width = xMax - xMin;
var height = yMax - yMin;
var translateX = -xMin;
var translateY = height + yMin;
var group = new TransformGroup();
group.Children.Add(new TranslateTransform(translateX, -translateY));
group.Children.Add(new ScaleTransform(canvas.ActualWidth / width, canvas.ActualHeight / -height));
canvas.RenderTransform = group;
return canvas;
}
private const int earthRadius = 6367;
private Point ToCanvas(double lat, double lon)
{
lon = ConvertToRadians(lon);
lat = ConvertToRadians(lat);
double x = earthRadius * Math.Cos(lat) * Math.Cos(lon); //((lon * my_canvas.ActualWidth) / 360.0) - 180.0; ;// //
double y = earthRadius * Math.Cos(lat) * Math.Sin(lon);// ((lat * my_canvas.ActualHeight) / 180.0) - 90.0;
return new Point(x, y);
}
public double ConvertToRadians(double angle)
{
return (Math.PI / 180) * angle;
}
As you can see in my result it's almost perfect, we can recognize the 2 countries but why are they in the wrong place ? (I would like to have them like this : http://geology.com/world/world-map.gif
Am I missing something ?
Based on this link, my result may be displayed with "Azimuthal (projections onto a plane)" while I would prefer this : http://en.wikipedia.org/wiki/Equirectangular_projection
So I'm thinking of changing my function "ToCanvas" but I don't know the component "the standard parallels (north and south of the equator) where the scale of the projection is true;"
Bonus question : What's the best way to get the border from a country in GPS coordinates ? I would like to draw the border of USA for example, so I guessed I could just get coordinates of borders to draw the country.
I've tried several websites, managed to get a .shp file from there : http://www.naturalearthdata.com/downloads/110m-cultural-vectors/, but I didn't manage to retrieve borders coordinates from there.
Thank you
EDIT Seems better with this code :
private Point ToCanvas(double lat, double lon)
{
// Equirectangular projection
double x1 = lon * Math.Cos(ConvertToRadians(lat));
double y1 = lat;
//lon = ConvertToRadians(lon);
//lat = ConvertToRadians(lat);
//double x = earthRadius * Math.Cos(lat) * Math.Cos(lon); //((lon * my_canvas.ActualWidth) / 360.0) - 180.0; ;// //
//double y = earthRadius * Math.Cos(lat) * Math.Sin(lon);// ((lat * my_canvas.ActualHeight) / 180.0) - 90.0;
return new Point(x1 * 10, y1 * 10);
}