I'm working with the sharpDX branch of the helix-toolkit library for a project at my university (HelixToolKit Library)
Currently I'm searching for a way to correctly select elements in a viewport.
I found a helpful example in the source code of the helixtoolkit library:example code on GitHub
public class MyLineGeometryModel3D : LineGeometryModel3D
{
private Color? initialColor = null;
public override bool HitTest(Ray rayWS, ref List<HitTestResult> hits)
{
if (initialColor == null)
{
initialColor = this.Color;
}
var result = base.HitTest(rayWS, ref hits);
var pressedMouseButtons = Viewport3DX.GetPressedMouseButtons();
if (pressedMouseButtons == 0 || pressedMouseButtons.HasFlag(MouseButtons.Left))
{
this.Color = result ? Color.Red : this.initialColor.Value;
}
return result;
}
}
I managed to get this running in my application. However instead of selecting only the topmost element, all elements intersected by the ray are selected.
Some kind of handler function is probably needed to highlight the element with the shortest distance?
I was checking some of the standard WPF solutions for that and they often use an eventhandler. ( e.g. 3D Hit Testing in WPF )
private void m_viewport3d_MouseDown(object sender, MouseButtonEventArgs e)
{
Point mousePos = e.GetPosition(m_viewport3d);
PointHitTestParameters hitParams = new PointHitTestParameters(mousePos);
HitTestResult result = VisualTreeHelper.HitTest(m_viewport3d, mousePos);
RayMeshGeometry3DHitTestResult rayMeshResult = result as RayMeshGeometry3DHitTestResult;
if (rayMeshResult != null)
{
MeshGeometry3D mesh = new MeshGeometry3D();
mesh.Positions.Add(rayMeshResult.MeshHit.Positions[rayMeshResult.VertexIndex1]);
mesh.Positions.Add(rayMeshResult.MeshHit.Positions[rayMeshResult.VertexIndex2]);
mesh.Positions.Add(rayMeshResult.MeshHit.Positions[rayMeshResult.VertexIndex3]);
mesh.TriangleIndices.Add(0);
mesh.TriangleIndices.Add(1);
mesh.TriangleIndices.Add(2);
GeometryModel3D marker = new GeometryModel3D(mesh, new DiffuseMaterial(Brushes.Blue));
//...add marker to the scene...
}
}
Is using an eventhandler a sensible solution ? And if yes how to get a ray element for calling the HitTest function in the eventhandler?
Use the Viewport method FindNearest:
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
Viewport3DX vp = e.Source as Viewport3DX;
Point3D p;
Vector3D v;
Model3D m;
if (vp.FindNearest(e.GetPosition(vp), out p, out v, out m))
{
//Do something with the found object
}
}
So, I actually found a solution myself, which is probably not perfect. But maybe this will be of use for somebody.
private void ViewPort3D_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Ray ray = this.ViewPort3D.UnProject(new Vector2((float)e.GetPosition(ViewPort3D).X, (float)e.GetPosition(ViewPort3D).Y));
var hits = new List<HitTestResult>();
// dictionary for connecting the id of the specific element and its distance
var hitElements = new Dictionary<int, double>();
// loop over all MeshGeometryModel3D elements
foreach (var geometry in Geometrys)
{
var isHit = geometry.Model3D.HitTest(ray, ref hits);
if (isHit)
{
hitElements.Add(geometry.Id, hits[hits.Count - 1].Distance);
}
}
if (hits.Count > 0)
{
double minDistance = hitElements.First().Value;
int id_of_hit_element = hitElements.First().Key;
foreach (var hit in hitElements)
{
if (hit.Value < minDistance)
{
minDistance = hit.Value;
id_of_hit_element = hit.Key;
}
}
var topElement = Geometrys.Find(geometry => geometry.Id == id_of_hit_element);
// do something with top element
}
}
P.S. Not a computer scientist btw, just a civil engineering student trying his best xD
Related
I'm trying to make an application for a homework assignment that adds a RectangleGeometry to the path.data as GeometryGroup Children and then updates the size while I hold down the mouse button and move the mouse and then finalizes it when on mouseup. The problem is that I need the subsequent rectangles to be drawn over top of the preceeding ones. Setting FillRule to nonzero doesn't accomplish this because it still shows the stroke of the underlying rectangles. So how can I accomplish this?
private void path_MouseDown(object sender, MouseButtonEventArgs e)
{
path.Stroke = strokeColor;
path.Fill = fillColor;
path.StrokeThickness = thickness;
p = Mouse.GetPosition((UIElement)sender);
gg = path.Data as GeometryGroup;
gg.Children.Add(new RectangleGeometry());
}
private void path_MouseMove(object sender, MouseEventArgs e)
{
if (p == null) return;
var pp = Mouse.GetPosition((UIElement)sender);
gg = path.Data as GeometryGroup;
if (index == 0)
{
var pg = gg.Children.Last() as RectangleGeometry;
pg.Rect = new Rect(p.Value, pp);
}
else if(index == 1)
{
var pg = gg.Children.Last() as RectangleGeometry;
pg.RadiusX = pp.X - p.Value.X;
pg.RadiusY = pp.Y - p.Value.Y;
pg.Rect = new Rect(p.Value, pp);
}
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
p = null;
}
I have a WinForms application where a number of lines are drawn in a TeeChart component. It is requested that it shall be possible to delete a line by right-clicking it.
Everything works fine, the clickseries event is captured and so on, but the user finds it difficult to hit the line on right click. The question is, is it possible to increase the region where the Line/FastLine object is sensible for clicking? That is, make the line wider without drawing the line any wider on the screen.
Tnx in advance
Yes, this is possible. The key to achieve that is PointInLineTolerance method. To achieve what you request you can combine it with NearestPoint's tool GetNearestPoint method as shown in this example:
public Form1()
{
InitializeComponent();
InitializeChart();
}
private void InitializeChart()
{
tChart1.Aspect.View3D = false;
tChart1.Series.Add(new Steema.TeeChart.Styles.Line()).FillSampleValues();
tChart1.MouseMove += TChart1_MouseMove;
}
private void TChart1_MouseMove(object sender, MouseEventArgs e)
{
var nearestPoint = new Steema.TeeChart.Tools.NearestPoint(tChart1[0]);
nearestPoint.Active = false;
var p = new Point(e.X, e.Y);
var index = nearestPoint.GetNearestPoint(p);
if (index != -1)
{
const int tolerance = 10;
var px = tChart1[0].CalcXPos(index);
var py = tChart1[0].CalcYPos(index);
var index2 = (index == tChart1[0].Count - 1) ? index - 1 : index + 1;
var qx = tChart1[0].CalcXPos(index2);
var qy = tChart1[0].CalcYPos(index2);
if (Steema.TeeChart.Drawing.Graphics3D.PointInLineTolerance(p, px, py, qx, qy, tolerance))
{
tChart1.Header.Text = "point " + index.ToString() + " clicked";
}
else
{
tChart1.Header.Text = "No point";
}
}
An alternative could be using an invisible fake series with same data as the original series.
I have a program for Kinect that is supposed to take the Skeletons from 50 frames, calculate the average one and draw it.
An async event handler is added to SkeletonFrameReady
private async void InitSkeletonEvent(object sender,SkeletonFrameReadyEventArgs e)
{
List<Vector3D> vectors = new List<Vector3D>();
double[,] coordinates = new double[50, 3];
double[,] finalCoordinates = new double[50, 3];
using (SkeletonFrame initSkeletonFrame = e.OpenSkeletonFrame())
{
if (initSkeletonFrame != null)
{
if (initSkeletons == null)
{
initSkeletons = new Skeleton[initSkeletonFrame.SkeletonArrayLength];
}
initSkeletonFrame.CopySkeletonDataTo(initSkeletons);
Skeleton initSkeleton = initSkeletons.Where(s => s.TrackingState == SkeletonTrackingState.Tracked).FirstOrDefault();
if (initSkeleton != null)
{
foreach (Skeleton s in initSkeletons)
{
// Get all tracked Skeletons for the 50 frames
while (frame < maxFrames)
{
frame += 1;
vectors = await getCoordinatesofJoints(initSkeleton);
coordinates = await getCoordinatesPerJoint(vectors);
}
}
// Remove the event from SkeletonFrame
this.sensor.SkeletonFrameReady -= InitSkeletonEvent;
finalCoordinates = await getAverageCoordinateForJoint(coordinates);
skeleton = await getAvgSkeleton(finalCoordinates);
}
}
}
}
There are several methods that are called with the await parameter. However they do not seem to complete their work and the returned value is not correct.
Here is an example of one of the functions. The rest are written analogically.
Am I using the async and await incorrectly?
private Task<List<Vector3D>> getCoordinatesofJoints(Skeleton s)
{
List<Vector3D> vectors = new List<Vector3D>();
foreach (Joint j in s.Joints)
{
Vector3D tmp = new Vector3D(j.Position.X, j.Position.Y, j.Position.Z);
vectors.Add(tmp);
}
return Task.Run(() => vectors);
}
My current program allows the user to click a point, then click another point (at least 20 pixels away) and draws a line between the 2 points. I've used a Polyline so that this can be done multiple times. Though the set of all the lines only appear after all the click are done.
void DrawingCanvas_MouseUp(object sender, MouseButtonEventArgs e) {
Point position = e.GetPosition(this);
if (leftList == null) {
//starting a new set
leftList.Add(position);
lastPoint = position;
return;
}
//calculate distance, i.e. end click
double a = lastPoint.X - position.X;
double b = lastPoint.Y - position.Y;
double distance = Math.Sqrt(a * a + b * b);
if (distance > 20) {
//continue to add to list
leftList.Add(position);
lastPoint = position;
} else {
//end of the line
paint();
leftList = new PointCollection();
}
}
private void paint() {
Polyline line = new Polyline();
line.Visibility = System.Windows.Visibility.Visible;
line.StrokeThickness = 2;
line.Stroke = System.Windows.Media.Brushes.Black;
line.Points = leftList;
myCanvas.Children.Add(line);
}
So my question is two-fold:
A) How do I make it so that after each click the new line is immediately added.
B) How do I render a line between the last point and where the mouse cursor is currently at (i.e. just before you choose your next point)
The following simple example starts drawing a new polyline when the left mouse button is pressed and the mouse is moved by the minimum point distance of 20, with the button kept pressed. It draws the last polyline segment (to the current mouse position) in either red or green, depending on its length. If the mouse button is released and the length of the new segment is >= 20, a new point is appended to the polyline. Otherwise the polyline is terminated, and a new polyline can be created.
private Polyline polyline;
private Polyline segment = new Polyline { StrokeThickness = 2 };
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (polyline == null)
{
var canvas = (Canvas)sender;
var point = e.GetPosition(canvas);
// create new polyline
polyline = new Polyline { Stroke = Brushes.Black, StrokeThickness = 2 };
polyline.Points.Add(point);
canvas.Children.Add(polyline);
// initialize current polyline segment
segment.Stroke = Brushes.Red;
segment.Points.Add(point);
segment.Points.Add(point);
canvas.Children.Add(segment);
}
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (polyline != null)
{
// update current polyline segment
var canvas = (Canvas)sender;
segment.Points[1] = e.GetPosition(canvas);
var distance = (segment.Points[0] - segment.Points[1]).Length;
segment.Stroke = distance >= 20 ? Brushes.Green : Brushes.Red;
}
}
private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (polyline != null)
{
var canvas = (Canvas)sender;
segment.Points[1] = e.GetPosition(canvas);
var distance = (segment.Points[0] - segment.Points[1]).Length;
if (distance >= 20)
{
polyline.Points.Add(segment.Points[1]);
segment.Points[0] = segment.Points[1];
}
else
{
if (polyline.Points.Count < 2)
{
canvas.Children.Remove(polyline);
}
polyline = null;
segment.Points.Clear();
canvas.Children.Remove(segment);
}
}
}
please maintain a collection of points on every click. in collection you can add one class which will have two properties like StartPoint and EndPoint.
when the mouse is clicked first time just add one class object to collection having start point only.
and when you click the mouse next time, ad end point to the last object of the class and meanwhile create a new object and assign this point as its start point and add it to collection, after that call the paint function.
I have a chart and I want the user to see the values when the pointer is on the points.
By using digEmAll's help in the page finding the value of the points in a chart ,I could write the following code:
Point? prevPosition = null;
ToolTip tooltip = new ToolTip();
void chart1_MouseMove(object sender, MouseEventArgs e)
{
var pos = e.Location;
if (prevPosition.HasValue && pos == prevPosition.Value)
return;
tooltip.RemoveAll();
prevPosition = pos;
var results = chart1.HitTest(pos.X, pos.Y, false, ChartElementType.PlottingArea);
foreach (var result in results)
{
if (result.ChartElementType == ChartElementType.PlottingArea)
{
chart1.Series[0].ToolTip = "X=#VALX, Y=#VALY";
}
}
}
by the above code,the user can see the values when the pointer is near to a series.But now How can I let the user to see the values only when the pointer is on the points?
I replaced
int k = result.PointIndex;
if (k >= 0)
{
chart1.Series[0].Points[k].ToolTip = "X=#VALX, Y=#VALY";
}
instead of
chart1.Series[0].ToolTip = "X=#VALX, Y=#VALY";
to solve my problem.But It wasn't usefull.
You should modify the code in this way:
Point? prevPosition = null;
ToolTip tooltip = new ToolTip();
void chart1_MouseMove(object sender, MouseEventArgs e)
{
var pos = e.Location;
if (prevPosition.HasValue && pos == prevPosition.Value)
return;
tooltip.RemoveAll();
prevPosition = pos;
var results = chart1.HitTest(pos.X, pos.Y, false,
ChartElementType.DataPoint);
foreach (var result in results)
{
if (result.ChartElementType == ChartElementType.DataPoint)
{
var prop = result.Object as DataPoint;
if (prop != null)
{
var pointXPixel = result.ChartArea.AxisX.ValueToPixelPosition(prop.XValue);
var pointYPixel = result.ChartArea.AxisY.ValueToPixelPosition(prop.YValues[0]);
// check if the cursor is really close to the point (2 pixels around the point)
if (Math.Abs(pos.X - pointXPixel) < 2 &&
Math.Abs(pos.Y - pointYPixel) < 2)
{
tooltip.Show("X=" + prop.XValue + ", Y=" + prop.YValues[0], this.chart1,
pos.X, pos.Y - 15);
}
}
}
}
}
The idea is to check if the mouse is very close to the point e.g. 2 pixels around it (because is really unlikely to be exactly on the point) and show the tooltip in that case.
Here's a complete working example.
I would take this solution:
Add custom tooltip event handler:
this.chart1.GetToolTipText += this.chart1_GetToolTipText;
Implement event handler:
private void chart1_GetToolTipText(object sender, ToolTipEventArgs e)
{
// Check selected chart element and set tooltip text for it
switch (e.HitTestResult.ChartElementType)
{
case ChartElementType.DataPoint:
var dataPoint = e.HitTestResult.Series.Points[e.HitTestResult.PointIndex];
e.Text = string.Format("X:\t{0}\nY:\t{1}", dataPoint.XValue, dataPoint.YValues[0]);
break;
}
}
Consider the following as a possible better option than tooltips...use the label feature of the chart control.
DataPoint _prevPoint;
void chart1_MouseMove(object sender, MouseEventArgs e)
{
// this if statement clears the values from the previously activated point.
if (_prevPoint) {
_prevPoint.MarkerStyle = MarkerStyle.None;
_prevPoint.IsValueShownAsLabel = false;
}
var result = chart1.HitTest(e.X, e.Y, ChartElementType.DataPoint);
if (result.ChartElementType == ChartElementType.DataPoint)
{
var prop = result.Object as DataPoint;
if (prop != null)
{
prop.IsValueShownAsLabel = true;
prop.MarkerStyle = MarkerStyle.Star4;
}
}
}
I've tested this and i'm using it currently. It's very nice on charts with a lot of points since it shows the marker on the chart as well.