private void CreateScene(bool useDXEngineHitTesting) { ObjectsPlaceholder.Children.Clear(); if (_dxEventManager3D != null) { _dxEventManager3D.ResetEventSources3D(); _dxEventManager3D = null; } if (_wpfEventManager != null) { _wpfEventManager.ResetEventSources3D(); _wpfEventManager = null; } if (_instancedMeshGeometryVisual3D != null) { _instancedMeshGeometryVisual3D.Dispose(); } _stopwatch = new Stopwatch(); _stopwatch.Start(); Mouse.OverrideCursor = Cursors.Wait; // Create InstancedGeometryVisual3D _instancedMeshGeometryVisual3D = new InstancedMeshGeometryVisual3D(_instanceMeshGeometry3D); _instancedMeshGeometryVisual3D.InstancesData = _instancedData; // Setup hit testing if (useDXEngineHitTesting) { // Use DXEventManager3D from Ab3d.DXEngine - it has optimized hit testing for instanced objects _dxEventManager3D = new Ab3d.DirectX.Utilities.DXEventManager3D(MainDXViewportView); var visualEventSource3D = new Ab3d.DirectX.Utilities.VisualEventSource3D(_instancedMeshGeometryVisual3D); visualEventSource3D.MouseEnter += delegate(object sender, DirectX.Common.EventManager3D.Mouse3DEventArgs e) { var dxRayInstancedHitTestResult = e.RayHitResult as DXRayInstancedHitTestResult; if (dxRayInstancedHitTestResult != null) { ProcessMouseEnter(dxRayInstancedHitTestResult.HitInstanceIndex); } }; visualEventSource3D.MouseMove += delegate(object sender, DirectX.Common.EventManager3D.Mouse3DEventArgs e) { var dxRayInstancedHitTestResult = e.RayHitResult as DXRayInstancedHitTestResult; if (dxRayInstancedHitTestResult != null) { ProcessMouseMove(dxRayInstancedHitTestResult.HitInstanceIndex); } }; visualEventSource3D.MouseLeave += delegate(object sender, DirectX.Common.EventManager3D.Mouse3DEventArgs e) { ProcessMouseLeave(); }; _dxEventManager3D.RegisterEventSource3D(visualEventSource3D); } else { // // IMPORTANT: // // To make WPF hit testing work (also used by EventManager3D), you need to set the IsWpfHitTestVisible to true. // This increases initialization time because WPF objects needs to be created for each instance, but this makes the WPF hit testing work. _instancedMeshGeometryVisual3D.IsWpfHitTestVisible = true; _wpfEventManager = new Ab3d.Utilities.EventManager3D(MainViewport); // Because Viewport3D is actually not shown, we need to specify different WPF's object for the source of mouse events - this could be MainDXViewportView or even better a parent Border _wpfEventManager.CustomEventsSourceElement = MainDXViewportView; var visualEventSource3D = new Ab3d.Utilities.VisualEventSource3D(_instancedMeshGeometryVisual3D); visualEventSource3D.MouseEnter += delegate(object sender, Mouse3DEventArgs e) { if (e.RayHitResult == null || e.RayHitResult.ModelHit == null) { return; // This should not happen, but it is safer to have this check anyway } // Get instance index of the hit object int hitInstanceIndex = GetHitInstanceIndex(e.RayHitResult); ProcessMouseEnter(hitInstanceIndex); }; visualEventSource3D.MouseMove += delegate(object sender, Mouse3DEventArgs e) { if (e.RayHitResult == null || e.RayHitResult.ModelHit == null) { return; // This should not happen, but it is safer to have this check anyway } // Get instance index of the hit object int hitInstanceIndex = GetHitInstanceIndex(e.RayHitResult); ProcessMouseMove(hitInstanceIndex); }; visualEventSource3D.MouseLeave += delegate(object sender, Mouse3DEventArgs e) { ProcessMouseLeave(); }; _wpfEventManager.RegisterEventSource3D(visualEventSource3D); } ObjectsPlaceholder.Children.Add(_instancedMeshGeometryVisual3D); Mouse.OverrideCursor = null; // If we would only change the InstancedData we would need to call Update method (but here this is not needed because we have set the data for the first time) //_instancedGeometryVisual3D.Update(); }
public ModelRotatorSample() { InitializeComponent(); _normalMaterial = new DiffuseMaterial(Brushes.Silver); _selectedMaterial = new DiffuseMaterial(new SolidColorBrush(Color.FromArgb(150, 192, 192, 192))); // semi-transparent Silver _eventManager = new Ab3d.Utilities.EventManager3D(MainViewport); // Setup events on ModelRotatorVisual3D SelectedModelRotator.ModelRotateStarted += delegate(object sender, ModelRotatedEventArgs args) { if (_selectedBoxModel == null) { return; } // When a new rotation is started, we create a new AxisAngleRotation3D with the used Axis or rotation // During the rotation we will adjust the angle (inside ModelRotated event handler) _axisAngleRotation3D = new AxisAngleRotation3D(args.RotationAxis, 0); // Insert the new rotate transform before the last translate transform that positions the box var rotateTransform3D = new RotateTransform3D(_axisAngleRotation3D); AddTransform(_selectedBoxModel, rotateTransform3D); }; SelectedModelRotator.ModelRotated += delegate(object sender, ModelRotatedEventArgs args) { if (_selectedBoxModel == null) { return; } _axisAngleRotation3D.Angle = args.RotationAngle; }; SelectedModelRotator.ModelRotateEnded += delegate(object sender, ModelRotatedEventArgs args) { // Nothing to do here in this sample // The event handler is here only for description purposes }; // To create custom circle 3D model, use the CreateCustomCircleModelCallback. // The following commented code shows how the default circle 3D model is created //SelectedModelRotator.CreateCustomCircleModelCallback = delegate (Vector3D normalVector3D, double innerRadius, double outerRadius, Brush circleBrush) //{ // int segmentsCount = 30; // double circleHeight = outerRadius * 0.05; // Height is 20 times smaller than outer radius // var tubeMesh3D = new Ab3d.Meshes.TubeMesh3D(new Point3D(0, 0, 0), normalVector3D, outerRadius, innerRadius, circleHeight, segmentsCount); // var geometryModel3D = new GeometryModel3D(tubeMesh3D.Geometry, new DiffuseMaterial(circleBrush)); // return geometryModel3D; //}; CreateRandomScene(); }
private void CreateSceneObjects() { for (int y = 0; y < 5; y++) { for (int x = 0; x < 6; x++) { var boxVisual3D = new Ab3d.Visuals.BoxVisual3D() { CenterPosition = new Point3D(40 * x - 100, 6, y * 40 - 80), Size = new Size3D(10, 10, 10), Material = _standardMaterial }; SelectionRootModelVisual3D.Children.Add(boxVisual3D); } } // Use EventManager3D to handle clicking on selected boxes var eventManager3D = new Ab3d.Utilities.EventManager3D(MainViewport); var multiVisualEventSource3D = new MultiVisualEventSource3D(SelectionRootModelVisual3D.Children); multiVisualEventSource3D.MouseEnter += delegate(object sender, Mouse3DEventArgs e) { Mouse.OverrideCursor = Cursors.Hand; var hitBoxVisual3D = e.HitObject as Ab3d.Visuals.BoxVisual3D; if (hitBoxVisual3D != null) { _wireBoxVisual3D.CenterPosition = hitBoxVisual3D.CenterPosition; _wireBoxVisual3D.Size = hitBoxVisual3D.Size; MainViewport.Children.Add(_wireBoxVisual3D); } }; multiVisualEventSource3D.MouseLeave += delegate(object sender, Mouse3DEventArgs e) { Mouse.OverrideCursor = null; if (_wireBoxVisual3D != null) { MainViewport.Children.Remove(_wireBoxVisual3D); } }; multiVisualEventSource3D.MouseClick += delegate(object sender, MouseButton3DEventArgs e) { if (_selectedBoxVisual3D != null) { _selectedBoxVisual3D.Material = _standardMaterial; } var hitBoxVisual3D = e.HitObject as Ab3d.Visuals.BoxVisual3D; if (hitBoxVisual3D != null) { hitBoxVisual3D.Material = _selectedMaterial; _selectedBoxVisual3D = hitBoxVisual3D; if (_wireBoxVisual3D != null) { MainViewport.Children.Remove(_wireBoxVisual3D); } MoveCameraTo(hitBoxVisual3D); } else { _selectedBoxVisual3D = null; } }; eventManager3D.RegisterEventSource3D(multiVisualEventSource3D); // Exclude _wireBoxVisual3D from receiving mouse events eventManager3D.RegisterExcludedVisual3D(_wireBoxVisual3D); }
public void ShowData(List <SphereData> originalData, Rect3D displayedDataBounds, Rect xyDataRange) { // Now use original data to create our data view objects // All data will be displayed in a 3D box defined below: //var displayedDataBounds = new Rect3D(AxesBox.CenterPosition.X - AxesBox.Size.X * 0.5, // AxesBox.CenterPosition.Y - AxesBox.Size.Y * 0.5, // AxesBox.CenterPosition.Z - AxesBox.Size.Z * 0.5, // AxesBox.Size.X, // AxesBox.Size.Y, // AxesBox.Size.Z); _allSpheresData = new List <SphereDataView>(originalData.Count); foreach (var originalSphereData in originalData) { // Set color of the sphere based on its y position // We choose the color from the gradient defined in EnsureGradientColorsArray double relativeY = (originalSphereData.Location.Y - xyDataRange.Y) / xyDataRange.Height; int colorArrayIndex = (int)(relativeY * (_gradientColorsArray.Length - 1)); Color color = _gradientColorsArray[colorArrayIndex]; var sphereDataView = SphereDataView.Create(originalSphereData, displayedDataBounds, originalData.Count, xyDataRange, color); sphereDataView.IsSelectedChanged += delegate(object sender, EventArgs args) { var changedPositionDataView = (SphereDataView)sender; if (changedPositionDataView.IsSelected) { DataListBox.SelectedItems.Add(changedPositionDataView); } else { DataListBox.SelectedItems.Remove(changedPositionDataView); } UpdateSelectedSpheresData(); }; _allSpheresData.Add(sphereDataView); } // Bind positions data to ListBox DataListBox.ItemsSource = _allSpheresData; // Create curve through all positions // This is done by first creating all points that define the curve (10 points between each position that define the curve) List <Point3D> allPositions = new List <Point3D>(); foreach (var positionData in _allSpheresData) { allPositions.Add(positionData.Position); } BezierCurve bezierCurve = Ab3d.Utilities.BezierCurve.CreateFromCurvePositions(allPositions); Point3DCollection curvePoints = bezierCurve.CreateBezierCurve(positionsPerSegment: 10); // How many points between each defined position in the curve // Create 3D Polyline from curvePoints Model3D curveModel = Ab3d.Models.Line3DFactory.CreatePolyLine3D(curvePoints, thickness: 2, color: Colors.Blue, isClosed: false, startLineCap: LineCap.Flat, endLineCap: LineCap.Flat, parentViewport3D: MainViewport); CurveModelVisual.Content = curveModel; // Now create 3D sphere objects for each position SpheresModelVisual.Children.Clear(); // Each sphere will also need MouseEnter, MouseLeave and MouseClick event handlers // We use EventManager3D for that var eventManager3D = new Ab3d.Utilities.EventManager3D(MainViewport); // Add 3D sphere for each position foreach (var positionData in _allSpheresData) { SpheresModelVisual.Children.Add(positionData.ModelVisual3D); // Sphere Visual3D is created in SphereDataView object // Add event handlers (sphere Visual3D will be the source of the events) var visualEventSource3D = new VisualEventSource3D(positionData.ModelVisual3D); visualEventSource3D.MouseEnter += delegate(object sender, Mouse3DEventArgs e) { if (_isSelecting) { return; } // Use hand cursor Mouse.OverrideCursor = Cursors.Hand; // Find selected position data var selectedPositionData = _allSpheresData.FirstOrDefault(p => p.ModelVisual3D == e.HitObject); SelectData(selectedPositionData, e.CurrentMousePosition); }; visualEventSource3D.MouseLeave += delegate(object sender, Mouse3DEventArgs e) { if (_isSelecting) { return; } Mouse.OverrideCursor = null; DataToolTipBorder.Visibility = Visibility.Collapsed; DataToolTipBorder.DataContext = null; SelectedSphereLinesVisual.Children.Clear(); }; visualEventSource3D.MouseClick += delegate(object sender, MouseButton3DEventArgs e) { // Select / deselect on mouse click var clickedPositionData = _allSpheresData.FirstOrDefault(p => p.ModelVisual3D == e.HitObject); if (clickedPositionData != null) { positionData.IsSelected = !clickedPositionData.IsSelected; } }; // Register the event source eventManager3D.RegisterEventSource3D(visualEventSource3D); } }
public ModelMoverOverlaySample() { InitializeComponent(); // We need to synchronize the Camera and Lights in OverlayViewport with the camera in the MainViewport Camera1.CameraChanged += delegate(object s, CameraChangedRoutedEventArgs args) { OverlayViewport.Camera = MainViewport.Camera; OverlayViewportLight.Direction = ((DirectionalLight)Camera1.CameraLight).Direction; }; // NOTE: // To define custom axes directions for ModelMoverVisual3D, define it in code and specify the axes direction it the constructor - for example: // ModelMover = new ModelMoverVisual3D(new Vector3D(-1, 0, 0), new Vector3D(0, -1, 0), new Vector3D(0, 0, -1)); // // You can even define angles that are not aligned with coordinate axes: // ModelMover = new ModelMoverVisual3D(new Vector3D(-1, -1, 0), new Vector3D(-1, 1, 0), new Vector3D(0, 0, -1)); // Setup event handlers on ModelMoverVisual3D ModelMover.ModelMoveStarted += delegate(object o, EventArgs eventArgs) { if (_selectedBoxModel == null) { return; } _startMovePosition = _selectedBoxModel.CenterPosition; }; ModelMover.ModelMoved += delegate(object o, Ab3d.Common.ModelMovedEventArgs e) { if (_selectedBoxModel == null) { return; } var newCenterPosition = _startMovePosition + e.MoveVector3D; if (Math.Abs(newCenterPosition.X) > 2000 || Math.Abs(newCenterPosition.Y) > 2000 || Math.Abs(newCenterPosition.Z) > 2000) { InfoTextBlock.Text = "Move out of range"; return; } _selectedBoxModel.CenterPosition = newCenterPosition; ModelMover.Position = GetSelectedModelWorldPosition(); // GetSelectedModelPosition gets the _selectedBoxModel.CenterPosition and transforms it with the transformations of parent ModelVisual3D objects InfoTextBlock.Text = string.Format("MoveVector3D: {0:0}", e.MoveVector3D); }; ModelMover.ModelMoveEnded += delegate(object sender, EventArgs args) { InfoTextBlock.Text = ""; }; _normalMaterial = new DiffuseMaterial(Brushes.Silver); _eventManager = new Ab3d.Utilities.EventManager3D(MainViewport); CreateRandomScene(); }
public DucksLakeDemo() { InitializeComponent(); MouseCameraControllerInfo1.AddCustomInfoLine(0, MouseCameraController.MouseAndKeyboardConditions.LeftMouseButtonPressed, "Select duck"); _duckModel3D = LoadDuckModel(); _eventManager3D = new Ab3d.Utilities.EventManager3D(MainViewport); _eventManager3D.CustomEventsSourceElement = ViewportBorder; MainDXViewportView.DXSceneInitialized += delegate(object sender, EventArgs args) { if (MainDXViewportView.DXScene == null) { return; // Probably WPF 3D rendering } // Setup shadow rendering _varianceShadowRenderingProvider = new VarianceShadowRenderingProvider(); // Because we have a big green plane, we need to increase the shadow map size (increase this more to get a more detailed shadow). _varianceShadowRenderingProvider.ShadowMapSize = 1024; MainDXViewportView.DXScene.InitializeShadowRendering(_varianceShadowRenderingProvider); // Specify the light that cases shadow(note that point light is not supported - only DirectionalLight and SpotLight are supported). MainSceneLight.SetDXAttribute(DXAttributeType.IsCastingShadow, true); }; this.Focusable = true; // by default Page is not focusable and therefore does not receive keyDown event this.PreviewKeyDown += OnPreviewKeyDown; this.Focus(); // We need to synchronize the Camera in OverlayViewport with the camera in the MainViewport Camera1.CameraChanged += delegate(object s, CameraChangedRoutedEventArgs args) { OverlayViewport.Camera = MainViewport.Camera; }; this.Loaded += delegate(object sender, RoutedEventArgs args) { GenerateRandomDucks(10); ShowModelMover(); }; // IMPORTANT: // It is very important to call Dispose method on DXSceneView after the control is not used any more (see help file for more info) this.Unloaded += delegate(object sender, RoutedEventArgs args) { if (_varianceShadowRenderingProvider != null) { _varianceShadowRenderingProvider.Dispose(); } MainDXViewportView.Dispose(); }; }
private void SubscribeEvents() { // Create an instance of EventManager3D for out Viewport3D // Important: // It is highly recommended not to have more than one EventManager3D object per Viewport3D. // Having multiple EventManager3D object can greatly reduce the performance because each time the Viewport3D camera is changed, // each EventManager3D must perform a full 3D hit testing from the current mouse position. // This operation is very CPU intensive and can affect performance when there are many 3D objects in the scene. // When multiple EventManager3D object are defined, then the 3D hit testing is performed multiple times. // Therefore it is recommended to have only one EventManager3D object per Viewport3D. // // It is also recommended to remove registered event sources after they are not used any more. // This can be done with RemoveEventSource3D method. _eventManager = new Ab3d.Utilities.EventManager3D(MainViewport); // Create a NamedObjects dictionary and set it to eventManager. // This way we can use names on EventSource objects to identify Model3D or Visual3D. // This is very usefull if we read the 3d objects with Reader3ds or ReaderObj and already have the NamedObjects dictionry. // When names are used in EventSource objects, we get the HitModelName or HitVisualName property set so we can know which object was hit. var namedObjects = new Dictionary <string, object>(); namedObjects.Add("Landscape", _landscapeModel); namedObjects.Add("Base", _windGenarator.BaseModel); namedObjects.Add("Blades", _windGenarator.BladesModel); namedObjects.Add("Turbine", _windGenarator.TurbineModel); _eventManager.NamedObjects = namedObjects; // subscribe the Landscape as the drag surface var modelEventSource = new Ab3d.Utilities.ModelEventSource3D(); // Instead of using TargetModelName and NamedObjects, we could also specify the model with TargetModel3D //modelEventSource.TargetModel3D = _landscapeModel; modelEventSource.TargetModelName = "Landscape"; modelEventSource.IsDragSurface = true; _eventManager.RegisterEventSource3D(modelEventSource); // subscribe other objects var multiModelEventSource = new Ab3d.Utilities.MultiModelEventSource3D(); // Instead of using TargetModelNames and NamedObjects, we could specify the models with model instances //multiModelEventSource.TargetModels3D = new Model3D[] { _windGenarator.BaseModel, // _windGenarator.BladesModel, // _windGenarator.TurbineModel }; // Because we have set the NamedObjects dictionary to _eventManager we can simply specify the models by their names: multiModelEventSource.TargetModelNames = "Base, Blades, Turbine"; multiModelEventSource.IsDragSurface = false; // Subscribe to all events multiModelEventSource.MouseEnter += eventSource_MouseEnter; multiModelEventSource.MouseLeave += eventSource_MouseLeave; multiModelEventSource.MouseMove += eventSource_MouseMove; multiModelEventSource.MouseUp += eventSource_MouseUp; multiModelEventSource.MouseDown += eventSource_MouseDown; multiModelEventSource.MouseClick += eventSource_MouseClick; multiModelEventSource.MouseWheel += multiModelEventSource_MouseWheel; multiModelEventSource.MouseDoubleClick += eventSource_MouseDoubleClick; multiModelEventSource.BeginMouseDrag += eventSource_BeginMouseDrag; multiModelEventSource.MouseDrag += eventSource_MouseDrag; multiModelEventSource.EndMouseDrag += eventSource_EndMouseDrag; multiModelEventSource.TouchEnter += eventSource_TouchEnter; multiModelEventSource.TouchDown += eventSource_TouchDown; multiModelEventSource.TouchMove += eventSource_TouchMove; multiModelEventSource.TouchUp += eventSource_TouchUp; multiModelEventSource.TouchLeave += eventSource_TouchLeave; // NOTE: // It is also possible to subscribe to touch manipulations events (pinch scale and rotate) // But this requires to set IsManipulationEnabled to true and that disables some other events // See the TouchManipulationsSample for more information _eventManager.RegisterEventSource3D(multiModelEventSource); }
public ModelMoverOverlaySample() { InitializeComponent(); // We need to synchronize the Camera and Lights in OverlayViewport with the camera in the MainViewport Camera1.CameraChanged += delegate(object s, CameraChangedRoutedEventArgs args) { OverlayViewport.Camera = MainViewport.Camera; OverlayViewportLight.Direction = ((DirectionalLight)Camera1.CameraLight).Direction; }; // NOTE: // To define custom axes directions for ModelMoverVisual3D, define it in code and specify the axes direction it the constructor - for example: // ModelMover = new ModelMoverVisual3D(new Vector3D(-1, 0, 0), new Vector3D(0, -1, 0), new Vector3D(0, 0, -1)); // // You can even define angles that are not aligned with coordinate axes: // ModelMover = new ModelMoverVisual3D(new Vector3D(-1, -1, 0), new Vector3D(-1, 1, 0), new Vector3D(0, 0, -1)); // Setup event handlers on ModelMoverVisual3D ModelMover.ModelMoveStarted += delegate(object o, EventArgs eventArgs) { if (_selectedBoxModel == null) { return; } _startMovePosition = _selectedBoxModel.CenterPosition; }; ModelMover.ModelMoved += delegate(object o, Ab3d.Common.ModelMovedEventArgs e) { if (_selectedBoxModel == null) { return; } var newCenterPosition = _startMovePosition + e.MoveVector3D; if (Math.Abs(newCenterPosition.X) > 2000 || Math.Abs(newCenterPosition.Y) > 2000 || Math.Abs(newCenterPosition.Z) > 2000) { InfoTextBlock.Text = "Move out of range"; return; } _selectedBoxModel.CenterPosition = newCenterPosition; ModelMover.Position = _selectedBoxModel.CenterPosition; InfoTextBlock.Text = string.Format("MoveVector3D: {0:0}", e.MoveVector3D); }; ModelMover.ModelMoveEnded += delegate(object sender, EventArgs args) { InfoTextBlock.Text = ""; }; _normalMaterial = new DiffuseMaterial(Brushes.Silver); _eventManager = new Ab3d.Utilities.EventManager3D(MainViewport); CreateRandomScene(); // IMPORTANT: // It is very important to call Dispose method on DXSceneView after the control is not used any more (see help file for more info) this.Unloaded += (sender, args) => MainDXViewportView.Dispose(); }
public void ShowData(List <SphereData> originalData) { // Now use original data to create our data view objects // All data will be displayed in a 3D box defined below: var displayedDataBounds = new Rect3D(-40, -20, -40, 80, 40, 80); _allPositionsData = new List <SphereDataView>(originalData.Count); foreach (var originalSphereData in originalData) { var sphereDataView = SphereDataView.Create(originalSphereData, displayedDataBounds, originalData.Count, _locationSize, _maxSize); sphereDataView.IsSelectedChanged += delegate(object sender, EventArgs args) { var changedPositionDataView = (SphereDataView)sender; if (changedPositionDataView.IsSelected) { DataListBox.SelectedItems.Add(changedPositionDataView); } else { DataListBox.SelectedItems.Remove(changedPositionDataView); } UpdateSelectedSpheresData(); }; _allPositionsData.Add(sphereDataView); } // Setup axis limits and shown values AxisWireBox.MinYValue = 0; AxisWireBox.MaxYValue = 10; AxisWireBox.YAxisValues = new double[] { 0, 2, 4, 6, 8, 10 }; // Bind positions data to ListBox DataListBox.ItemsSource = _allPositionsData; // Create curve through all positions // This is done by first creating all points that define the curve (10 points between each position that define the curve) List <Point3D> allPositions = new List <Point3D>(); foreach (var positionData in _allPositionsData) { allPositions.Add(positionData.Position); } BezierCurve bezierCurve = Ab3d.Utilities.BezierCurve.CreateFromCurvePositions(allPositions); Point3DCollection curvePoints = bezierCurve.CreateBezierCurve(positionsPerSegment: 10); // How many points between each defined position in the curve // Create 3D Polyline from curvePoints Model3D curveModel = Ab3d.Models.Line3DFactory.CreatePolyLine3D(curvePoints, thickness: 2, color: Colors.Blue, isClosed: false, startLineCap: LineCap.Flat, endLineCap: LineCap.Flat, parentViewport3D: MainViewport); CurveModelVisual.Content = curveModel; // Now create 3D sphere objects for each position SpheresModelVisual.Children.Clear(); // Each sphere will also need MouseEnter, MouseLeave and MouseClick event handlers // We use EventManager3D for that var eventManager3D = new Ab3d.Utilities.EventManager3D(MainViewport); // Add 3D sphere for each position foreach (var positionData in _allPositionsData) { SpheresModelVisual.Children.Add(positionData.ModelVisual3D); // Sphere Visual3D is created in SphereDataView object // Add event handlers (sphere Visual3D will be the source of the events) var visualEventSource3D = new VisualEventSource3D(positionData.ModelVisual3D); visualEventSource3D.MouseEnter += delegate(object sender, Mouse3DEventArgs e) { if (_isSelecting) { return; } // Use hand cursor Mouse.OverrideCursor = Cursors.Hand; // Find selected position data var selectedPositionData = _allPositionsData.FirstOrDefault(p => p.ModelVisual3D == e.HitObject); SelectData(selectedPositionData, e.CurrentMousePosition); }; visualEventSource3D.MouseLeave += delegate(object sender, Mouse3DEventArgs e) { if (_isSelecting) { return; } Mouse.OverrideCursor = null; DataToolTipBorder.Visibility = Visibility.Collapsed; DataToolTipBorder.DataContext = null; SelectedSphereLinesVisual.Children.Clear(); }; visualEventSource3D.MouseClick += delegate(object sender, MouseButton3DEventArgs e) { // Select / deselect on mouse click var clickedPositionData = _allPositionsData.FirstOrDefault(p => p.ModelVisual3D == e.HitObject); if (clickedPositionData != null) { positionData.IsSelected = !clickedPositionData.IsSelected; } }; // Register the event source eventManager3D.RegisterEventSource3D(visualEventSource3D); } }