private void RecreateControlPointNumbers() { if (_controlPoints == null) { return; } OverlayCanvas.Children.Clear(); for (int i = 0; i < _controlPoints.Count; i++) { TextBlock textBlock = new TextBlock(); textBlock.FontSize = 10; textBlock.Foreground = Brushes.Red; textBlock.FontWeight = FontWeights.Bold; textBlock.Text = i.ToString(); // Calculate the 2D position of the 3D control point Point point2D = Camera1.Point3DTo2D(_controlPoints[i]); Canvas.SetLeft(textBlock, point2D.X + 10); Canvas.SetTop(textBlock, point2D.Y - 7); OverlayCanvas.Children.Add(textBlock); } }
private void UpdateOverlayCanvasElements() { if (!this.IsLoaded) { return; } // Start with right top back edge of the Box //var position3D = GoldBoxVisual3D.CenterPosition + new Vector3D(-GoldBoxVisual3D.Size.X / 2, GoldBoxVisual3D.Size.Y / 2, GoldBoxVisual3D.Size.Z / 2); var position3D = GoldBoxVisual3D.CenterPosition + new Vector3D(0, GoldBoxVisual3D.Size.Y / 2, 0); if (_overlayBrushHeight < 0.001) { OverlayInfoTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); _overlayBrushHeight = OverlayInfoTextBlock.DesiredSize.Height + OverlayInfoBorder.BorderThickness.Bottom + OverlayInfoBorder.BorderThickness.Top; } var pos1 = Camera1.Point3DTo2D(position3D); var pos2 = pos1 + new Vector(30, -20); OverlayLine.X1 = pos1.X; OverlayLine.Y1 = pos1.Y; OverlayLine.X2 = pos2.X; OverlayLine.Y2 = pos2.Y; Canvas.SetLeft(OverlayInfoBorder, pos2.X); Canvas.SetTop(OverlayInfoBorder, pos2.Y - _overlayBrushHeight / 2); }
private void ViewportBorder_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (_currentObjectCreationState != ObjectCreationStates.SelectBaseSize) { return; } double boxWidth = _selectedObjectScaleTransform.ScaleX; double boxDepth = _selectedObjectScaleTransform.ScaleZ; if (boxWidth * boxDepth < 1) { // Box too small - user probably only clicked with the mouse and did not make a drag to define the base size // Remove the added object _currentObjectCreationState = ObjectCreationStates.SelectStartPoint; ObjectsVisual.Children.Remove(_selectedModelVisual3D); return; } _selectHeightStartMousePosition = e.GetPosition(MainViewport); // Now we will measure how much is one pixel on screen in 3D // This will be used in setting the height of the object with moving the mouse up Point3D p1 = _lastIntersectionPoint; Point3D p2 = new Point3D(p1.X, p1.Y + 1, p1.Z); Point p1_screen = Camera1.Point3DTo2D(p1); Point p2_screen = Camera1.Point3DTo2D(p2); // We have the base object - now define the height _screenTo3DFactor = 1.0 / Math.Abs(p2_screen.Y - p1_screen.Y); if (CreateBoxButton.IsChecked ?? false) { _currentObjectCreationState = ObjectCreationStates.SelectHeight; } else { _currentObjectCreationState = ObjectCreationStates.SelectStartPoint; } }
private void UpdateControlPointNumbers() { if (OverlayCanvas.Children.Count == 0) { return; } for (int i = 0; i < OverlayCanvas.Children.Count; i++) { TextBlock textBlock = (TextBlock)OverlayCanvas.Children[i]; // Calculate the 2D position of the 3D control point Point point2D = Camera1.Point3DTo2D(_controlPoints[i]); Canvas.SetLeft(textBlock, point2D.X + 10); Canvas.SetTop(textBlock, point2D.Y - 7); } }
private void SelectionOverlayCanvasOnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { // We do not start selecting immediately when mouse is down // but wait until mouse is moved for a few pixels. // This prevents interfering with handling mouse click events _isSelectStarting = true; Point position = e.GetPosition(SelectionOverlayCanvas); _startSelectionPosition = position; // Calculate screen positions for all spheres so that we can do a quick check if a sphere is inside selection rectangle foreach (var positionData in _allSpheresData) { positionData.ScreenPosition = Camera1.Point3DTo2D(positionData.Position); } e.Handled = true; }
private void UpdateDescriptionConnectionLine() { var sampleData = _samplesData[_currentSampleIndex]; Point3D targetObjectPosition = new Point3D(sampleData.Matrix.M41, sampleData.Matrix.M42, sampleData.Matrix.M43); targetObjectPosition.Z -= SamplesDistance * _currentSampleIndex; double descriptionBorderXPos = Canvas.GetLeft(DesciptionOuterBorder); double descriptionBorderYPos = Canvas.GetTop(DesciptionOuterBorder); Point objectPositionOnScreen = Camera1.Point3DTo2D(targetObjectPosition); DescriptionConnectionLine.X1 = objectPositionOnScreen.X; DescriptionConnectionLine.Y1 = objectPositionOnScreen.Y; DescriptionConnectionLine.X2 = descriptionBorderXPos; DescriptionConnectionLine.Y2 = descriptionBorderYPos + 20; Canvas.SetLeft(DescriptionConnectionRectangle, objectPositionOnScreen.X - (DescriptionConnectionRectangle.Width * 0.5)); Canvas.SetTop(DescriptionConnectionRectangle, objectPositionOnScreen.Y - (DescriptionConnectionRectangle.Height * 0.5)); }
private void CalculateTextBlockPositions(int startTriangleIndiceIndex, out Point[] screenPositions, out int[] indexes) { indexes = new int[3]; indexes[0] = _rootMesh.TriangleIndices[startTriangleIndiceIndex]; indexes[1] = _rootMesh.TriangleIndices[startTriangleIndiceIndex + 1]; indexes[2] = _rootMesh.TriangleIndices[startTriangleIndiceIndex + 2]; double sumX = 0; double sumY = 0; screenPositions = new Point[indexes.Length]; for (int i = 0; i < indexes.Length; i++) { Point3D onePosition = _rootMesh.Positions[indexes[i]]; Point screenPosition = Camera1.Point3DTo2D(onePosition); sumX += screenPosition.X; sumY += screenPosition.Y; screenPositions[i] = screenPosition; } Point centerScreenPosition = new Point(sumX / indexes.Length, sumY / indexes.Length); // Get direction vectors for (int i = 0; i < indexes.Length; i++) { Vector vector = screenPositions[i] - centerScreenPosition; vector.Normalize(); screenPositions[i] += vector * 10; // move text center 10 pixels away from the vertex position } }
//public void SetBoxPosition(Vector3D offset) //{ // AnimatedBoxTranslate.OffsetX = offset.X; // AnimatedBoxTranslate.OffsetY = offset.Y; // AnimatedBoxTranslate.OffsetZ = offset.Z; // UpdateOverlayData(); //} private void UpdateOverlayData() { // // Convert Point3D (sphere's CenterPosition) to 2D position on the screen with using camera.Point3DTo2D method // var sphereCenterPosition = Sphere1Visual3D.CenterPosition; sphereCenterPosition = Sphere1Visual3D.Transform.Transform(sphereCenterPosition); var spheresCenterOnScreen = Camera1.Point3DTo2D(sphereCenterPosition); // NOTE: // The above method requires that the camera is attached to real a Viewport3D // If you need to convert 3D coordinates to 2D space without creating Viewport3D, // you can use the overloaded method that takes viewportSize as parameter - for example: //var targetPositionCamera = new Ab3d.Cameras.TargetPositionCamera() //{ // Heading = 30, // Attitude = -20, // Distance = 200 //}; //var point2D = targetPositionCamera.Point3DTo2D(new Point3D(100, 100, 100), viewportSize: new Size(200, 100)); // Update UI only when the difference is significant (more than 1 pixel) bool isSphere1ScreenPositionChangedSignificantly = (Math.Abs(spheresCenterOnScreen.X - _oldSphere1ScreenPosition.X) >= 1.0 || Math.Abs(spheresCenterOnScreen.Y - _oldSphere1ScreenPosition.Y) >= 1.0); if (isSphere1ScreenPositionChangedSignificantly) { Sphere1ConnectionLine.X1 = spheresCenterOnScreen.X; Sphere1ConnectionLine.Y1 = spheresCenterOnScreen.Y; Sphere1ConnectionLine.X2 = Sphere1ConnectionLine.X1 + 30; Sphere1ConnectionLine.Y2 = Sphere1ConnectionLine.Y1 - 15; Sphere1InfoTextBlock.Text = string.Format("Screen position\r\nby Point3DTo2D:\r\nx:{0:0} y:{1:0}", spheresCenterOnScreen.X, spheresCenterOnScreen.Y); Canvas.SetLeft(Sphere1InfoBorder, spheresCenterOnScreen.X + 29); Canvas.SetTop(Sphere1InfoBorder, spheresCenterOnScreen.Y - Sphere1InfoBorder.ActualHeight - 14); _oldSphere1ScreenPosition = spheresCenterOnScreen; } // // Convert Rect3D (Box's Bounds) to 2D Rect on the screen with using camera.Rect3DTo2D method // var boxBounds3D = Box1Visual3D.Content.Bounds; boxBounds3D = Box1Visual3D.Transform.TransformBounds(boxBounds3D); Rect screenBoxBounds2D = Camera1.Rect3DTo2D(boxBounds3D); // Update UI only when the difference is significant (more than 1 pixel) if (Math.Abs(screenBoxBounds2D.X - _oldBox1ScreenBounds.X) >= 1.0 || Math.Abs(screenBoxBounds2D.Y - _oldBox1ScreenBounds.Y) >= 1.0 || Math.Abs(screenBoxBounds2D.Width - _oldBox1ScreenBounds.Width) >= 1.0 || Math.Abs(screenBoxBounds2D.Height - _oldBox1ScreenBounds.Height) >= 1.0) { Canvas.SetLeft(Box1OverlayRectangle, screenBoxBounds2D.X); Canvas.SetTop(Box1OverlayRectangle, screenBoxBounds2D.Y); Box1OverlayRectangle.Width = screenBoxBounds2D.Width; Box1OverlayRectangle.Height = screenBoxBounds2D.Height; Box1ConnectionLine.X1 = screenBoxBounds2D.X + screenBoxBounds2D.Width - 1; Box1ConnectionLine.Y1 = screenBoxBounds2D.Y + 1; Box1ConnectionLine.X2 = Box1ConnectionLine.X1 + 20; Box1ConnectionLine.Y2 = Box1ConnectionLine.Y1 - 10; Box1InfoTextBlock.Text = string.Format("Screen bounds by\r\nRect3DTo2D:\r\nx:{0:0} y:{1:0}\r\nw:{2:0} h:{3:0}", screenBoxBounds2D.X, screenBoxBounds2D.Y, screenBoxBounds2D.Width, screenBoxBounds2D.Height); Canvas.SetLeft(Box1InfoBorder, screenBoxBounds2D.X + screenBoxBounds2D.Width + 18); Canvas.SetTop(Box1InfoBorder, screenBoxBounds2D.Y - Box1InfoBorder.ActualHeight - 8); _oldBox1ScreenBounds = screenBoxBounds2D; } if (isSphere1ScreenPositionChangedSignificantly) // reuse the test done for Sphere1 { var sphereGeometryModel3D = Sphere2Visual3D.Content as GeometryModel3D; if (sphereGeometryModel3D != null) { var meshGeometry3D = (MeshGeometry3D)sphereGeometryModel3D.Geometry; var positionsCount = meshGeometry3D.Positions.Count; if (_sphere2MeshScreenPositions == null || _sphere2MeshScreenPositions.Length != positionsCount) { // Do not create new array on each UI update _sphere2MeshScreenPositions = new Point[positionsCount]; _sphere2Ellipses = new Ellipse[positionsCount]; for (int i = 0; i < positionsCount; i++) { var ellipse = new Ellipse() { Fill = Brushes.Yellow, Width = 4, Height = 4 }; OverlayCanvas.Children.Add(ellipse); _sphere2Ellipses[i] = ellipse; } } // Points3DTo2D also support parallel calculations (for lots of positions the perf gains are significant). // Tests show that it is worth using parallel algorithm when number of positions is more the 300 (but this may be highly CPU dependent) bool useParallelFor = positionsCount > 300; bool success = Camera1.Points3DTo2D(points3D: meshGeometry3D.Positions, points2D: _sphere2MeshScreenPositions, transform: Sphere2Visual3D.Transform, useParallelFor: useParallelFor); if (success) // success can be false when the Viewport3D is not initialized (does not have its size) or the camera is not yet initialized { double xSum = 0; double ySum = 0; int samplesCount = 0; double halfEllipseWidth = _sphere2Ellipses[0].Width / 2; double halfEllipseHeight = _sphere2Ellipses[0].Width / 2; for (var i = 0; i < _sphere2Ellipses.Length; i++) { var ellipse = _sphere2Ellipses[i]; double x = _sphere2MeshScreenPositions[i].X; double y = _sphere2MeshScreenPositions[i].Y; if (double.IsNaN(x) || double.IsNaN(y)) // This may happen when object is on the camera's near plane { continue; } Canvas.SetLeft(ellipse, x - halfEllipseWidth); Canvas.SetTop(ellipse, y - halfEllipseHeight); xSum += x; ySum += y; samplesCount++; } // Calculate the center by averaging the screen positions: double xCenter = xSum / samplesCount; double yCenter = ySum / samplesCount; Sphere2ConnectionLine.X1 = xCenter; Sphere2ConnectionLine.Y1 = yCenter; Sphere2ConnectionLine.X2 = xCenter + 49; Sphere2ConnectionLine.Y2 = yCenter - 25; Canvas.SetLeft(Sphere2InfoBorder, xCenter + 49); Canvas.SetTop(Sphere2InfoBorder, yCenter - Sphere2InfoBorder.ActualHeight - 24); } } } //Point3D centerPoint3D; //Point centerPoint2D; //// In this case we could also calculate centerPoint2D from bounds2D (see commented line below). //// But to demonstrate the Point3DTo2D method we are not using bounds2D ////centerPoint2D = new Point(bounds2D.X + bounds2D.Width / 2, bounds2D.Y + bounds2D.Height / 2); //centerPoint3D = new Point3D(bounds3D.X + bounds3D.SizeX / 2, // bounds3D.Y + bounds3D.SizeY / 2, // bounds3D.Z + bounds3D.SizeZ / 2); //int segments = 200; //bool parallel = false; //var sphereMesh3D = new Ab3d.Meshes.SphereMesh3D(new Point3D(0,0,0), 20, segments).Geometry; //var points2D = new Point[sphereMesh3D.Positions.Count]; //var point3Ds = sphereMesh3D.Positions.ToArray(); ////var point3Ds = sphereMesh3D.Positions; ////point3Ds.Freeze(); //var stopwatch = new Stopwatch(); //stopwatch.Start(); //var translateTransform3D = new TranslateTransform3D(100, 0, 0); //bool isCorrect = false; //for (int i = 0; i < 10; i++) //{ // isCorrect = Camera1.Points3DTo2D(point3Ds, points2D, translateTransform3D, useParallelFor: parallel); //} //stopwatch.Stop(); //if (isCorrect) // MessageBox.Show("Time: " + stopwatch.Elapsed.TotalMilliseconds); //for (var i = 0; i < sphereMesh3D.Positions.Count; i++) //{ // var point3D = point3Ds[i]; // if (translateTransform3D != null) // point3D = translateTransform3D.Transform(point3D); // var point2D = Camera1.Point3DTo2D(point3D); // var distance = (point2D - points2D[i]).Length; // if (distance > 0.0001) // { // } //} // ADDITIONAL NOTE: // To convert 3D positions of a 3D LINE to screen positions, use the Line3DTo2D method instead of two calls to Point3DTo2D. // The Line3DTo2D method correctly handles the case when the 3D line crosses the camera near plane (goes behind the camera). // In this case the line needs to be cropped at the camera near plane, otherwise the results are incorrect. }