Brikker
Hvis vi starter med formen så er logikke rimelig enkel. Vi setter opp to brikker og vi plukker opp musebegivenheter.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace pieces
{
public partial class Form1 : Form
{
List<brikke> brikker = new List<brikke>(2);
brikke currentBrikke = null;
Boolean isRotating = false;
int lastX, lastY;
public Form1()
{
InitializeComponent();
// set up brikker: x,y,x,y,x,y
// close polygons! (last=first)
setupBrikke("10,1,10,100,50,100,10,1");
setupBrikke("100,100,200,100,150,200,100,100");
}
private void setupBrikke(String pos)
{
List<PointF> pnts = new List<PointF>(5);
String[] vals = pos.Split(',');
for (int ix = 0; ix < vals.Length; ix = ix + 2)
pnts.Add(new Point(Convert.ToInt32(vals[ix]),
Convert.ToInt32(vals[ix + 1])));
brikker.Add(new brikke(panel1, pnts.ToArray()));
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
// bottom to top
for (int ix = brikker.Count - 1; ix > -1; ix--)
brikker[ix].Draw();
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
// have we hit a polygon?
foreach (brikke b in brikker)
{
if (b.polygonHit(new Point(e.X, e.Y)))
{
currentBrikke = b;
// this one to top
brikker.Remove(currentBrikke);
brikker.Insert(0,currentBrikke);
currentBrikke.hiLite(true);
panel1.Invalidate(currentBrikke.getRegion());
lastX = e.X;
lastY = e.Y;
isRotating = currentBrikke.WillRotate(new Point(e.X, e.Y));
return;
}
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (currentBrikke != null)
{
//keep inside
int x = Math.Max(0,Math.Min(e.X, panel1.Width));
int y = Math.Max(0,Math.Min(e.Y, panel1.Height));
panel1.Invalidate(currentBrikke.getRegion());
if (isRotating)
currentBrikke.rotate(x,lastX,y,lastY);
else
currentBrikke.move(x-lastX,y-lastY);
panel1.Invalidate(currentBrikke.getRegion());
lastX = x;
lastY = y;
}
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
if (currentBrikke != null)
{
currentBrikke.hiLite(false);
panel1.Invalidate(currentBrikke.getRegion());
currentBrikke = null;
}
}
}
}
Vi ser at vi stiller noen spørsmål til en brikke: polygonHit, willRotate og vi bruker metodene: move, rotate, hilite, draw, getRegion. Vi ser dessuten at vi bruker en oppfriskingsstrategi der vi invaliderer det området en brikke dekker slik at den generell paint-metoden bare tegner det som er nødvendig. Dette for å unngå at hele brettet friskes opp.
Når vi initialiserer en brikke setter vi opp det omgivende rektangelet og vi bestemmer hva som er midten i brikken, polygonet.
// attributter
PointF[] polygon;
Rectangle box;
Point center;
static Brush HILITE_BRUSH = Brushes.Red;
static Brush NORMAL_BRUSH = Brushes.Blue;
Brush m_brush;
Control m_owner;
// current transformation matrix
Matrix m_Matrix=new Matrix();
public brikke(Control owner,PointF[] p)
{
// polygon is closed
polygon = p;
m_brush = NORMAL_BRUSH;
m_owner = owner;
makeBox();
makeCenter();
}
private void makeBox()
{
// surrounding rectangle
float minx = Int32.MaxValue;
float miny = Int32.MaxValue;
float maxx = Int32.MinValue;
float maxy = Int32.MinValue;
for (int ix = 0; ix < polygon.Length; ix++)
{
minx = Math.Min(minx, polygon[ix].X);
miny = Math.Min(miny, polygon[ix].Y);
maxx = Math.Max(maxx, polygon[ix].X);
maxy = Math.Max(maxy, polygon[ix].Y);
}
box = new Rectangle((int)minx - 2, (int)miny - 2,
(int)maxx - (int)minx + 4, (int)maxy - (int)miny + 4);
}
private void makeCenter()
{
// decide center
float xsum=0;
float ysum = 0;
for (int ix = 0; ix < polygon.Length - 1; ix++)
{
xsum += polygon[ix].X-box.Left;
ysum += polygon[ix].Y-box.Top;
}
center.X = Convert.ToInt32(box.Left+(xsum / (polygon.Length - 1)));
center.Y = Convert.ToInt32(box.Top+(ysum / (polygon.Length - 1)));
}
Metodene som responderer på de kravene Form1 har er implementert slik:
public Region getRegion()
{
Region r = new Region(new Rectangle(box.X - 2, box.Top - 2, box.Width + 4, box.Height + 4));
r.Transform(m_Matrix);
return r;
}
public void hiLite(Boolean on)
{
if (on)
m_brush = HILITE_BRUSH;
else
m_brush = NORMAL_BRUSH;
}
public void move(int dx, int dy)
{
m_Matrix.Translate(dx, dy,MatrixOrder.Append);
}
public void rotate(int x, int lastX, int y, int lastY)
{
Matrix m = m_Matrix.Clone();
m.Invert();
Point[] pts = new Point[2] { new Point(x, y), new Point(lastX, lastY) };
m.TransformPoints(pts);
// fixed anglesize
float v = 2.0f;
// direction based on quadrant and direction
if ( ((pts[0].X > center.X) && (pts[0].Y < pts[1].Y))
|| ((pts[0].X <= center.X) && (pts[0].Y >= pts[1].Y)))
v = -v;
m_Matrix.RotateAt(v,center, MatrixOrder.Prepend);
}
public void Draw()
{
Graphics DC = m_owner.CreateGraphics();
DC.ResetTransform();
DC.MultiplyTransform(m_Matrix);
DC.FillPolygon(m_brush, polygon);
DC.DrawLines(new Pen(Brushes.Black), polygon);
// mark center
DC.DrawLine(new Pen(Brushes.Black), center.X-10,center.Y,center.X+10,center.Y);
DC.DrawLine(new Pen(Brushes.Black), center.X,center.Y-10,center.X,center.Y+10);
// mark box
DC.DrawRectangle(new Pen(Brushes.DarkGray), box);
DC.ResetTransform();
}
public Boolean polygonHit(Point p)
{
Matrix m = m_Matrix.Clone();
m.Invert();
Point[] pts = new Point[1] { new Point(p.X, p.Y) };
m.TransformPoints(pts);
return calculations.InsidePolygon(polygon, pts[0].X,pts[0].Y);
}
public Boolean WillRotate(Point p)
{
Matrix m = m_Matrix.Clone();
m.Invert();
Point[] pts = new Point[1] { new Point(p.X, p.Y) };
m.TransformPoints(pts);
// reasonable distance from center ?
double dist = Math.Sqrt(
(pts[0].X - center.X) * (pts[0].X - center.X) +
(pts[0].Y - center.Y) * (pts[0].Y - center.Y));
return dist > Math.Min(box.Width, box.Height) / 3;
}
Vi ser at metoden polygonHit baserer seg på en klasse, calculations, og en metode InsidePolygon. Klassen calculations er slik.



