private void UpdateSelectedObjectsWithBoundsIn2D(Rect selectionRectangle) { // TODO: When there is many objects in the scene, then // when the selected is started we can convert the 3D bounds of all objects to 2D bounds. // Then in this method we would just call IntersectsWith without calling Rect3DTo2D. foreach (var model3D in _objectsModel3DGroup.Children) { var bounds = model3D.Bounds; // TODO: If parent Model3DGroup or parent Visual3D use any transformation, // then we need to transform the bounds with that transformation. // If the hierarchy of parents is not simple or not known, then you can use: // Ab3d.Utilities.TransformationsHelper.GetModelTotalTransform() or GetVisual3DTotalTransform() // Convert 3D object bounds to 2D bounds on the screen var bounds2D = Camera1.Rect3DTo2D(bounds); // Check if our selection rectangle intersects with the 2D object bounds if (selectionRectangle.IntersectsWith(bounds2D)) { _selectedObjects.Add(model3D); } } }
//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. }