private void Camera1OnCameraChanged(object sender, CameraChangedRoutedEventArgs cameraChangedRoutedEventArgs) { if (!(AlignWithCameraCheckBox.IsChecked ?? false)) { return; } // On each change of camera we will update the orientation of billboard objects. // When using PlaneVisual3D, TextBlockVisual3D or TextVisual3D we can simply call the AlignWithCamera method. // If we did some changes to the Camera1 in the code, // it is recommended to call Refresh before calling AlignWithCamera method: // Camera1.Refresh(); bool fixYAxis = FixYAxisCheckBox.IsChecked ?? false; PlaneVisual1.AlignWithCamera(Camera1); TextBlockVisual3D1.AlignWithCamera(Camera1); if (fixYAxis) { // To show a billboard with fixed Y axis, we first call AlignWithCamera and then set the UpDirection back to Y axis. PlaneVisual1.HeightDirection = Ab3d.Common.Constants.UpVector; // = new Vector3D(0, 1, 0); TextBlockVisual3D1.UpDirection = Ab3d.Common.Constants.UpVector; } if (FixScreenSizeCheckBox.IsChecked ?? false) { // When FixScreenSizeCheckBox is checked, we update the size of the billboards in 3D works // so that they are rendered to the same size on the screen. // // This is done with the GetWorldSize method that calculates a size in 3D world coordinates from a size provided in 2D screen coordinates. // You can also use CameraUtils.GetOrthographicWorldSize and CameraUtils.GetPerspectiveWorldSize methods to get the same results but with more low level parameters. // // It is also possible to get the opposite value - size on screen - with the following methods: // Camera1.GetScreenSize(Size worldSize, Point3D targetPosition3D) // CameraUtils.GetPerspectiveScreenSize(Size worldSize, double lookDirectionDistance, double fieldOfView, Size viewport3DSize) // CameraUtils.GetOrthographicScreenSize(Size worldSize, double cameraWidth, Size viewport3DSize) // Set the size so that it will be shown in 200 x 40 box on the screen. Size worldSize = Camera1.GetWorldSize(new Size(200, 40), TextBlockVisual3D1.Position); // With TextBlockVisual3D we can use multiple methods to adjust the size of the text: // 1) Change FontSize property - this is the most performance expensive method because this will require to update the text (and render the bitmap if it is used to show the content of TextBlockVisual3D - for example when in DXEngine). // 2) Change Size property - this is better but would require to update the plane's MeshGeometry3D. This creates a lot of objects on each camera change and require frequent garbage collections. // TextBlockVisual3D1.Size = worldSize; // 3) Scale the TextBlockVisual3D1 to the required size. This is by far the fastest and most efficient method because only the transformation is changed: var scaleTransform3D = TextBlockVisual3D1.Transform as ScaleTransform3D; if (scaleTransform3D == null) { scaleTransform3D = new ScaleTransform3D(); // To prevent scaling the position (multiplying it with the scale factor), we need to set the center of scale scaleTransform3D.CenterX = TextBlockVisual3D1.Position.X; scaleTransform3D.CenterY = TextBlockVisual3D1.Position.Y; scaleTransform3D.CenterZ = TextBlockVisual3D1.Position.Z; TextBlockVisual3D1.Transform = scaleTransform3D; } // Calculate required scale to get the size of the TextBlockVisual3D to the required worldSize double scaleFactor = worldSize.Width / TextBlockVisual3D1.Size.Width; scaleTransform3D.ScaleX = scaleFactor; scaleTransform3D.ScaleY = scaleFactor; scaleTransform3D.ScaleZ = scaleFactor; // Do the same for PlaneVisual3D worldSize = Camera1.GetWorldSize(new Size(200, 40), PlaneVisual1.CenterPosition); // The easiest way to update the size of a PlaneVisual3D is to change its Size. // But because this creates a new MeshGeometry3D, this can produce a lot of garbage because this is called on each frame (on each camera change). // Therefore it is better to use ScaleTransform3D to update the size. //PlaneVisual1.Size = worldSize; scaleTransform3D = PlaneVisual1.Transform as ScaleTransform3D; if (scaleTransform3D == null) { scaleTransform3D = new ScaleTransform3D(); // To prevent scaling the position (multiplying it with the scale factor), we need to set the center of scale scaleTransform3D.CenterX = PlaneVisual1.CenterPosition.X; scaleTransform3D.CenterY = PlaneVisual1.CenterPosition.Y; scaleTransform3D.CenterZ = PlaneVisual1.CenterPosition.Z; PlaneVisual1.Transform = scaleTransform3D; } scaleFactor = worldSize.Width / PlaneVisual1.Size.Width; scaleTransform3D.ScaleX = scaleFactor; scaleTransform3D.ScaleY = scaleFactor; scaleTransform3D.ScaleZ = scaleFactor; } else { // When FixScreenSizeCheckBox is unchecked, reset the size back to initial size TextBlockVisual3D1.Transform = null; PlaneVisual1.Transform = null; // The following code is used if we do not use ScaleTransform3D but instead change the Size //if (TextBlockVisual3D1.Size.Width != 80) // TextBlockVisual3D1.Size = new Size(80, 20); //if (PlaneVisual1.Size.Width != 80) // PlaneVisual1.Size = new Size(80, 20); } // Update all tree objects foreach (var treePlaneVisual in TreesPlaceholerVisual3D.Children.OfType <PlaneVisual3D>()) { treePlaneVisual.AlignWithCamera(Camera1); if (fixYAxis) { treePlaneVisual.HeightDirection = Ab3d.Common.Constants.UpVector; } } // After objects were rotated, we can reorder them so that those farther away from the camera are rendered first. // This way the objects will be correctly visible through transparent objects. _transparencySorter.Sort(TransparencySorter.SortingModeTypes.ByCameraDistance); UpdateOverlayCanvasElements(); }