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.