I am writing a custom control which draws polygons.
I use matrix calculations to scale and shear the polygons so that they fit the control.
I need to know if the mouse has been clicked inside one of the polygons, so I am using ray casting.
This all seems to work fine individually, however I am now encountering an issue with retrieving the mouse coordinates relative to the display matrix im using.
I use the following code:
// takes the graphics matrix used to draw the polygons
Matrix mx = currentMatrixTransform;
// inverts it
mx.Invert();
// mouse position
Point[] pa = new Point[] { new Point(e.X, e.Y) };
// uses it to transform the current mouse position
mx.TransformPoints(pa);
return pa[0];
now this works for every other set of coordinates, by that I mean that one pair of mouse coordinates appears to give the correct values as if it has been through the matrix, but the one beside it gives a value as if it has not been through the matrix, below is an output of the mouse values received when moving down the control.
{X=51,Y=75}
{X=167,Y=251}
{X=52,Y=77}
{X=166,Y=254}
{X=52,Y=78}
{X=166,Y=258}
{X=52,Y=79}
{X=166,Y=261}
{X=52,Y=80}
{X=165,Y=265}
{X=52,Y=81}
{X=165,Y=268}
if it helps the matrix used to draw the polygons is
Matrix trans = new Matrix();
trans.Scale(scaleWidth, scaleHeight);
trans.Shear(italicFactor, 0.0F, MatrixOrder.Append);
trans.Translate(offsetX, offsetY, MatrixOrder.Append);
e.Graphics.Transform = trans;
currentMatrixTransform = e.Graphics.Transform;
Thanks in advance
You are inverting your matrix each time you call it.
Matrix is a class, meaning that by performing Invert() on mx, you are also performing it on currentMatrixTransform.
You can either copy the matrix using Clone() and then invert the clone,
or you can perform Invert() once again after you've transformed the point pa.
A second invert example:
// takes the graphics matrix used to draw the polygons
Matrix mx = currentMatrixTransform;
// inverts it
mx.Invert();
// mouse position
Point[] pa = new Point[] { new Point(e.X, e.Y) };
// uses it to transform the current mouse position
mx.TransformPoints(pa);
// inverts it back
max.Invert();
return pa[0];
A clone example:
// takes the graphics matrix used to draw the polygons
Matrix mx = currentMatrixTransform.Clone();
// inverts it
mx.Invert();
// mouse position
Point[] pa = new Point[] { new Point(e.X, e.Y) };
// uses it to transform the current mouse position
mx.TransformPoints(pa);
return pa[0];
Related
Im making a graph program and im stuck at where I need to get mouse coordinates to equal graphic scale. With picturebox I use transform to scale my graphic:
RectangleF world = new RectangleF(wxmin, wymin, wwid, whgt);
PointF[] device_points =
{
new PointF(0, PictureBox1.ClientSize.Height),
new PointF(PictureBox1.ClientSize.Width, PictureBox1.ClientSize.Height),
new PointF(0, 0),
};
Matrix transform = new Matrix(world, device_points);
gr.Transform = transform;
Im using MouseMove function. Is there a way to transform mouse coordinates? When I put my mouse on x=9 I need my mouse coordinate to be 9.
private void PictureBox1_MouseMove(object sender, MouseEventArgs e)
{
Console.WriteLine(e.X);
}
As Hans' comment implies, you can use a second Matrix to accomplish this. You can obtain it either by copying the original Matrix and calling the copy's Invert() method, or you can create the new Matrix from scratch by reversing your input rectangles from the original.
IMHO inverting is easier, but it does mean you'll need to create the inverse matrix and store it somewhere. E.g.:
Matrix transform = new Matrix(world, device_points);
gr.Transform = transform;
inverseTransform = transform.Clone();
inverseTransform.Invert();
where inverseTransform is a field in your class rather than a local variable, so that your mouse-handling code can use it later.
If you must construct the Matrix later, you could do it like this:
RectangleF device = new RectangleF(new Point(), PictureBox1.ClientSize);
PointF[] world_points =
{
new PointF(wxmin, wymin + whgt),
new PointF(wxmin + wwid, wymin + whgt),
new PointF(wxmin, wymin),
};
Matrix inverseTransform = new Matrix(device, world_points);
In either case, you'd simply use the Matrix.TransformPoints() method in your mouse-handling code to apply the inverse transform to the mouse coordinates to get back to your world coordinates.
I'm trying to draw the path of some text characters on a 3D plane for printing on a 3D printer. I'm getting gaps where the last line should be drawn. I also tried drawing a rectangle using the AddRectangle command. I ended up with a line.
Does anyone know what I might be doing wrong?
Here are are the code snippets showing what I've written:
graphics_path1.AddRectangle(new Rectangle(50,50,50,50));
graphics_path1.AddString("Test text...", new FontFamily(workingFontObject.FONT.Name), (int)workingFontObject.FONT.Style, workingFontObject.FONT.Size, new Point(8, 2), StringFormat.GenericTypographic);
RotateScaleOffsetGraphicsPath(graphics_path1, rotateAngle, RotateCenter, magnification, xyOffset);
private void RotateScaleOffsetGraphicsPath(GraphicsPath workingGraphicsPath, float RotateAngle, PointF RotateCenter, float Magnification, PointF ROIOffset)
{
float Invert = (1.0f); //invert without any other modifications
Matrix mm1 = new Matrix(); //Create a new matrix for transformations
PointF bedCenter = new PointF(bedWidth/2,bedDepth/2);
//mm1.RotateAt(RotateAngle, bedCenter, MatrixOrder.Append);//Rotate
mm1.Translate(Invert*ROIOffset.X, Invert* ROIOffset.Y, MatrixOrder.Append);//set roi offset
mm1.Scale(Magnification, Magnification, MatrixOrder.Append);//magnify, the 2 magnifications are x and y and can actually be different for a skewed growth
//apply the transformation matrix on the graphical path
workingGraphicsPath.Transform(mm1);
}
Blockquote
I have a rectangle with a square at its bottom. I also have code that makes the rectangle rotate around its origin which is at the top of this rectangle. Im trying to make the square at the bottom to always stay at the end of this rectangle even when its rotated. Hers a picture to illustrate my problem:
I see now that it wasn't such a good idea to make the square at the bottom white. So when i rotate the rectangle upwards to the right or upwards to the left, I want the square to keep staying at the end of this rectangle. Maybe there is a simple solution, but my knowledge isn't as good as it should be on this subject. Hope someone could point me in the right direction.
Something like this aught to get you there.
float pendulumAngle;
Vector2 origin;
Vector2 squareTLcorner;//top left corner of square
Vector2 squareOffset;
void Reset()
{
pendulumAngle = 0;
origin = new Vector2(?.?f, ?.?f);// set to whatever you need
squareTLcorner = new Vector2(?.?f, ?.?f); // set to whatever you need
squareOffset = squareTLcorner - origin;
}
void UpdatePendulum(float angleMovedSinceLastFrame)
{
pendulumAngle += angleMovedSinceLastFrame;
}
void UpdateSquarePosition()
{
squareTLcorner = Vector2.Transform(squareOffset, Matrix.CreateRotationZ(pendulumAngle) + origin;
}
void DrawSquare()
{
spriteBatch.Draw(sqTex,squareTLcorner, , ,pendulumAngle, , , , );// overload 6 of 7
}
The easiest way is passing a transformation matrix to the sprite batch.
Rectangle Black = new Rectangle(0,0, 20, 100);
Rectangle White = new Rectangle( Black.Left, Black.Bottom, Black.Width, Black.width);
Vector2 Pivot= new Vector(100,100);
vector2 Origin = new Vector2( 10,10);
Matrix transform = Matrix.CreateTranslation(-Origin.X, -Origin.Y)
* Matrix.CreateRotationZ(angle)
* Matrix.CreateTranslation(Pivot);
SpriteBatch.begin(...., transform)
SpriteBatch.Draw( Texture, Black, Color);
SpriteBatch.Draw( Texture, White, Color);
SpriteBatch.end();
Basically you are working in a different space that is rotated ans translated as you need, realize that Black rectangle location is (0,0).
Code it's not tested but should work as expected. ;)
I'm having a problem with moving a 3D object after I apply a rotation. Both the move and rotation functions work perfectly on their own. But the problem is when I move an object after a rotation, the object doesn't follow the mouse and goes in weird directions. If anyone can see my flaw, I'd appreciate it. Thanks! Here's my code:
private void Rotate()
{
double angle;
bool willangle = Double.TryParse(AngleRot.Text.ToString(), out angle);
RectangleVisual3D rect = (RectangleVisual3D)LastSelectedObject;
AxisAngleRotation3D r = new AxisAngleRotation3D(new Vector3D(0, 0, 1), angle);
RotateTransform3D rot = new RotateTransform3D(r, rect.Origin);
rect.Transform = Transform3DHelper.CombineTransform(rect.Transform, rot);
LastSelectedObject = rect as ModelVisual3D;
}
private void MoveObject(MouseEventArgs e)
{
if (LastSelectedObject is RectangleVisual3D)
{
RectangleVisual3D rect = (RectangleVisual3D)LastSelectedObject;
Point3D? origin = GetPoints(e);
if (origin == null)
return;
rect.Origin = (Point3D)origin;
LastSelectedObject = rect as ModelVisual3D;
}
}
I hope this help: The order of rotation and move is very important. If you move, then rotate, then it move according to the x,y,z co-ordinates. If you rotate, then move, then it will move according to the rotations co-ordinates.
Moving your object by setting its origin is generally a bad move. If your helper library ( I don't think Transform3DHelper is .Net? ) is doing matrix math in the basic way, then you're messing it up by setting rect.Origin.
Instead, try finding the distance vector moved and apply that translation matrix.
I'm assuming
Vector2D dist=new Vector2D((oldPosition - newPosition).x, (oldPosition - newPosition).y);
TranslateTransform3D trans = new TranslateTransform3D(dist.x,dist.y,0);
rect.Transform = Transform3DHelper.CombineTransform(rect.Transform, trans);
The other possible error is that CombineTransform should reverse rect.Transform and rot, but I'm not sure if the API is handling that for you. See if an overloaded version of the method allows you to reverse those two.
I am working on a simple game where you click on square sprites before they disappear. I decided to get fancy and make the squares rotate. Now, when I click on the squares, they don't always respond to the click. I think that I need to rotate the click position around the center of the rectangle(square) but I am not sure how to do this. Here is my code for the mouse click:
if ((mouse.LeftButton == ButtonState.Pressed) &&
(currentSquare.Contains(mouse.X , mouse.Y )))
And here is the rotation logic:
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
RotationAngle += elapsed;
float circle = MathHelper.Pi * 2;
RotationAngle = RotationAngle % circle;
I am new to Xna and programming in general, so any help is appreciated.
Thanks a lot,
Bill
So you're trying to determine if a point is in a rectangle, but when the rectangle is rotated?
The Contains() method will only work if the current rotation is 0 (I guess currentSquare is a rectangle representing the image position without rotation?).
What you will have to do is do the opposite rotation of the image on the mouse coordinates (the mouse coordinates should rotate around the origin of your image), then calculate if the new position is within currentSquare. You should be able to do all of this using vectors.
(Untested)
bool MouseWithinRotatedRectangle(Rectangle area, Vector2 tmp_mousePosition, float angleRotation)
{
Vector2 mousePosition = tmp_mousePosition - currentSquare.Origin;
float mouseOriginalAngle = (float)Math.Atan(mousePosition.Y / mousePosition.X);
mousePosition = new Vector2((float)(Math.Cos(-angleRotation + mouseOriginalAngle) * mousePosition.Length()),
(float)(Math.Sin(-angleRotation + mouseOriginalAngle) * , mousePosition.Length()));
return area.Contains(mousePosition);
}
If you dont need pixel pefect detection you can create bounding sphere for each piece like this.
var PieceSphere = new BoundingSphere()
{
Center =new Vector3(new Vector2(Position.X + Width/2, Position.Y + Height/2), 0f),
Radius = Width / 2
};
Then create another bounding sphere around mouse pointer.For position use mouse coordinates and for radius 1f. Because mouse pointer will be moving it will change its coordinates so you have to also update the sphere's center on each update.
Checking for clicks would be realy simple then.
foreach( Piece p in AllPieces )
{
if ((mouse.LeftButton == ButtonState.Pressed) && p.BoundingSphere.Intersects(MouseBoundingSphere))
{
//Do stuff
}
}
If you are lazy like me you could just do a circular distance check.
Assuming mouse and box.center are Vector2
#gets us C^2 according to the pythagorean Theorem
var radius = (box.width / 2).squared() + (box.height / 2).square
#distance check
(mouse - box.center).LengthSquared() < radius
Not perfectly accurate but the user would have a hard time noticing and inaccuracies that leave a hitbox slightly too large are always forgiven. Not to mention the check is incredibly fast just calculate the radius when the square is created.