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.
Related
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'm using the GDI+ function FillClosedCurve (in C# if that matters), to draw a series of points as a nice "rounded" curve area. The problem is that it appears to be adding a strange "loop" shape to one corner of the resulting shape. The screenshot shows this little extra loop at the top right corner of my red coloured area -
the code is
g.FillClosedCurve(shapeBrush, shapePoints.ToArray(), FillMode.Winding, 0.4f);
g.DrawPolygon(blackPen, shapePoints.ToArray());
I added a black border with the DrawPolygon function so you can see where my coordinates are.
Can anyone tell me why I get that weird loop shape at the top right corner ??
Thank you.
It's caused by you specifying the same point more than once in the array i.e. as the first and last point.
FillClosedCurve "closes" the path for you....so there is no need...in fact it's incorrect to specify the point twice....as it will then try and close the path from a point back to the point at the same position....which causes the artifact.
Here's a little example to demonstrate the difference:
private void Form1_Paint(object sender, PaintEventArgs e)
{
PointF[] arrayDuplicatedPointAtStartAndEnd =
{
new PointF(20.0F, 20.0F),
new PointF(150.0F, 50.0F),
new PointF(150.0F, 150.0F),
new PointF(20.0F, 20.0F),
};
PointF[] arrayWithoutPointOverlap =
{
new PointF(20.0F, 20.0F),
new PointF(150.0F, 50.0F),
new PointF(150.0F, 150.0F)
};
float tension = 0.4F;
using (SolidBrush redBrush = new SolidBrush(Color.Red))
{
e.Graphics.FillClosedCurve(redBrush, arrayDuplicatedPointAtStartAndEnd, FillMode.Winding, tension);
}
e.Graphics.TranslateTransform(110.0f, 0.0f, MatrixOrder.Prepend);
using (SolidBrush blueBrush = new SolidBrush(Color.Blue))
{
e.Graphics.FillClosedCurve(blueBrush, arrayWithoutPointOverlap, FillMode.Winding, tension);
}
}
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 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];
I have an application that is very "connection-based", i.e. multiple inputs/outputs.
The UI concept of a "cable" is exactly what I'm looking for to make the concept clear to the user. Propellerhead took a similar approach in their Reason software for audio components, illustrated in this YouTube video (fast forward to 2m:50s).
I can make this concept work in GDI by painting a spline from point A to point B, there's got to be a more elegant way to use Paths or something in WPF for this, but where do you start? Is there a good way to simulate the animation of the cable swing when you grab it and shake it?
I'm also open to control libraries (commercial or open source) if this wheel has already been invented for WPF.
Update: Thanks to the links in the answers so far, I'm almost there.
I've created a BezierCurve programmatically, with Point 1 being (0, 0), Point 2 being the bottom "hang" point, and Point 3 being wherever the mouse cursor is. I've created a PointAnimation for Point 2 with an ElasticEase easing function applied to it to give the "Swinging" effect (i.e., bouncing the middle point around a bit).
Only problem is, the animation seems to run a little late. I'm starting the Storyboard each time the mouse moves, is there a better way to do this animation? My solution so far is located here:
Bezier Curve Playground
Code:
private Path _path = null;
private BezierSegment _bs = null;
private PathFigure _pFigure = null;
private Storyboard _sb = null;
private PointAnimation _paPoint2 = null;
ElasticEase _eEase = null;
private void cvCanvas_MouseMove(object sender, MouseEventArgs e)
{
var position = e.GetPosition(cvCanvas);
AdjustPath(position.X, position.Y);
}
// basic idea: when mouse moves, call AdjustPath and draw line from (0,0) to mouse position with a "hang" in the middle
private void AdjustPath(double x, double y)
{
if (_path == null)
{
_path = new Path();
_path.Stroke = new SolidColorBrush(Colors.Blue);
_path.StrokeThickness = 2;
cvCanvas.Children.Add(_path);
_bs = new BezierSegment(new Point(0, 0), new Point(0, 0), new Point(0, 0), true);
PathSegmentCollection psCollection = new PathSegmentCollection();
psCollection.Add(_bs);
_pFigure = new PathFigure();
_pFigure.Segments = psCollection;
_pFigure.StartPoint = new Point(0, 0);
PathFigureCollection pfCollection = new PathFigureCollection();
pfCollection.Add(_pFigure);
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures = pfCollection;
_path.Data = pathGeometry;
}
double bottomOfCurveX = ((x / 2));
double bottomOfCurveY = (y + (x * 1.25));
_bs.Point3 = new Point(x, y);
if (_sb == null)
{
_paPoint2 = new PointAnimation();
_paPoint2.From = _bs.Point2;
_paPoint2.To = new Point(bottomOfCurveX, bottomOfCurveY);
_paPoint2.Duration = new Duration(TimeSpan.FromMilliseconds(1000));
_eEase = new ElasticEase();
_paPoint2.EasingFunction = _eEase;
_sb = new Storyboard();
Storyboard.SetTarget(_paPoint2, _path);
Storyboard.SetTargetProperty(_paPoint2, new PropertyPath("Data.Figures[0].Segments[0].Point2"));
_sb.Children.Add(_paPoint2);
_sb.Begin(this);
}
_paPoint2.From = _bs.Point2;
_paPoint2.To = new Point(bottomOfCurveX, bottomOfCurveY);
_sb.Begin(this);
}
If you want true dynamic motion (ie, when you "shake" the mouse pointer you can create waves that travel along the cord), you will need to use finite element techniques. However if you are ok with static behavior you can simply use Bezier curves.
First I'll briefly describe the finite element approach, then go into more detail on the static approach.
Dynamic approach
Divide your "cord" into a large number (1000 or so) "elements", each with a position and velocity Vector. Use the CompositionTarget.Rendering event to compute each element position as follows:
Compute the pull on each element along the "cord" from adjacent elements, which is proportional to the distance between elements. Assume the cord itself is massless.
Compute the net force vector on each "element" which consists of the pull from each adjacent element along the cord, plus the constant force of gravity.
Use a mass constant to convert the force vector to accelaration, and update the position and velocity using the equations of motion.
Draw the line using a StreamGeometry build with a BeginFigure followed by a PolyLineTo. With so many points there is little reason to do the extra computations to create a cubic bezier approximation.
Static approach
Divide your cord into perhaps 30 segments, each of which is a cubic bezier approximation to the catenary y = a cosh(x/a). Your end control points should be on the catenary curve, the parallels should tangent to the catenaries, and the control line lengths set based on the second derivative of the catenary.
In this case you will probably also want to render a StreamGeometry, using BeginFigure and PolyBezierTo to build it.
I would implement this as a custom Shape subclass "Catenary" similar to Rectangle and Ellipse. In that case, all you have to override the DefiningGeometry property. For efficiency I would also override CacheDefiningGeometry, GetDefiningGeometryBounds, and GetNaturalSize.
You would first decide how to parameterize your catenary, then add DependencyProperties for all your parameters. Make sure you set the AffectsMeasure and AffectsRender flags in your FrameworkPropertyMetadata.
One possible parameterization would be XOffset, YOffset, Length. Another might be XOffset, YOffset, SagRelativeToWidth. It would depend on what would be easiest to bind to.
Once your DependencyProperties are defined, implement your DefiningGeometry property to compute the cubic bezier control points, construct the StreamGeometry, and return it.
If you do this, you can drop a Catenary control anywhere and get a catenary curve.
User bezier curve segments in a path.
http://www.c-sharpcorner.com/UploadFile/dbeniwal321/WPFBezier01302009015211AM/WPFBezier.aspx
IMHO 'hanging' (physically simulated) cables are a case of over-doing it - favouring looks over usability.
Are you sure you're not just cluttering the user-experience ?
In a node/connection-based UI I find clear connections (like in Quartz Composer : http://ellington.tvu.ac.uk/ma/wp-content/uploads/2006/05/images/Quartz%20Composer_screenshot_011.png ) way more important than eye-candy like swinging cables that head in a different direction (down due to gravity) than where the actually connection-point is. (And in the mean time eat up CPU-cycles for the simulation that could be more useful elsewhere)
Just my $0.02