private void CreateRootModel() { bool show3DLine = false; switch (ObjectComboBox.SelectedIndex) { case 0: Ab3d.Meshes.SphereMesh3D sphere = new Ab3d.Meshes.SphereMesh3D(new Point3D(0, 0, 0), 80, 10); _rootMesh = sphere.Geometry; break; case 1: Ab3d.Meshes.SphereMesh3D sphere2 = new Ab3d.Meshes.SphereMesh3D(new Point3D(0, 0, 0), 80, 5); _rootMesh = sphere2.Geometry; break; case 2: // NOTE: Here we create an optimized version of sphere but it does not have texture coordinates // If we do not need texture coordinates (do not need to show texture), than we can slightly simplify the sphere model. // When we also need to create texture coordinates, the top most position that represent the same point in space is defined multiple times // - each time with slightly different texture coordinate (so that the whole top line on the texture is shown around the top sphere position). // Also there are x + 1 position around the sphere (in one row) - one additional position is added that the first and last position in the "row" are in the same space position, // but the first position has x texture coordinate 0, the last position has x texture coordinate 1 - this shows the whole texture nicely around the sphere. // If texture coordinates are not needed, than we do not need to define more than one upper and bottom positions, // and also do not need to add another position to the row. // Optimized sphere can be created only with SphereMesh3D object (not with SphereVisual3D) Ab3d.Meshes.SphereMesh3D sphere3 = new Ab3d.Meshes.SphereMesh3D(new Point3D(0, 0, 0), 80, 12, false); // false: generateTextureCoordinates _rootMesh = sphere3.Geometry; break; case 3: Ab3d.Meshes.BoxMesh3D box = new Ab3d.Meshes.BoxMesh3D(new Point3D(0, 0, 0), new Size3D(130, 60, 100), 1, 1, 1); _rootMesh = box.Geometry; break; case 4: Ab3d.Meshes.BoxMesh3D box2 = new Ab3d.Meshes.BoxMesh3D(new Point3D(0, 0, 0), new Size3D(130, 60, 100), 4, 4, 4); _rootMesh = box2.Geometry; break; case 5: Ab3d.Meshes.CylinderMesh3D cylinder = new Ab3d.Meshes.CylinderMesh3D(new Point3D(0, -50, 0), 60, 100, 12, true); _rootMesh = cylinder.Geometry; break; case 6: Ab3d.Meshes.ConeMesh3D cone = new Ab3d.Meshes.ConeMesh3D(new Point3D(0, -50, 0), 30, 60, 100, 12, true); _rootMesh = cone.Geometry; break; case 7: Ab3d.Meshes.ConeMesh3D cone2 = new Ab3d.Meshes.ConeMesh3D(new Point3D(0, -50, 0), 30, 60, 100, 6, false); _rootMesh = cone2.Geometry; break; case 8: var readerObj = new Ab3d.ReaderObj(); var teapotModel = readerObj.ReadModel3D(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Resources\ObjFiles\Teapot.obj")) as GeometryModel3D; if (teapotModel == null) { return; } // Get the teapot MeshGeometry3D _rootMesh = (MeshGeometry3D)teapotModel.Geometry; break; case 9: var line = new Ab3d.Visuals.LineVisual3D() { StartPosition = new Point3D(0, 0, 0), EndPosition = new Point3D(100, 0, 0), LineColor = Colors.Silver, LineThickness = 20 }; Show3DLines(line); show3DLine = true; break; case 10: var polyLineVisual3D = new Ab3d.Visuals.PolyLineVisual3D() { Positions = new Point3DCollection(new Point3D[] { new Point3D(-75, 50, 0), new Point3D(-25, 0, 0), new Point3D(25, 50, 0), new Point3D(75, 0, 0) }), LineThickness = 20 }; Show3DLines(polyLineVisual3D); show3DLine = true; break; case 11: // This is the same line as in the previous sample (PolyLineVisual3D), but this time // it is created as diconnected list of lines - note that this requires that the positions are duplicated. var multiLineVisual3D = new Ab3d.Visuals.MultiLineVisual3D() { Positions = new Point3DCollection(new Point3D[] { new Point3D(-75, 50, 0), new Point3D(-25, 0, 0), new Point3D(-25, 0, 0), new Point3D(25, 50, 0), new Point3D(25, 50, 0), new Point3D(75, 0, 0) }), LineThickness = 20 }; Show3DLines(multiLineVisual3D); show3DLine = true; break; default: _rootMesh = null; break; } // If we were looking at 3D lines before and now we are looking an standard 3D models, // we adjust the camera back to the side view (from direct front view) if (_isShowing3DLines && !show3DLine) { Camera1.Heading = 30; Camera1.Attitude = -20; Camera1.Distance = 300; } _isShowing3DLines = show3DLine; _rootModel = new GeometryModel3D(); _rootModel.Geometry = _rootMesh; _rootModel.Material = new DiffuseMaterial(Brushes.DarkBlue); ObjectModelVisual.Content = _rootModel; Ab3d.Utilities.ModelEventSource3D modelEventSource = new Ab3d.Utilities.ModelEventSource3D(); modelEventSource.TargetModel3D = _rootModel; modelEventSource.MouseClick += new Ab3d.Common.EventManager3D.MouseButton3DEventHandler(modelEventSource_MouseClick); _eventsManager.ResetEventSources3D(); _eventsManager.RegisterEventSource3D(modelEventSource); MainViewport.Cursor = Cursors.Hand; }
private void CreateRootModel() { switch (ObjectComboBox.SelectedIndex) { case 0: Ab3d.Meshes.BoxMesh3D box = new Ab3d.Meshes.BoxMesh3D(new Point3D(0, 0, 0), new Size3D(130, 60, 100), 1, 1, 1); _rootMesh = box.Geometry; break; case 1: Ab3d.Meshes.BoxMesh3D box2 = new Ab3d.Meshes.BoxMesh3D(new Point3D(0, 0, 0), new Size3D(130, 60, 100), 4, 4, 4); _rootMesh = box2.Geometry; break; case 2: Ab3d.Meshes.SphereMesh3D sphere = new Ab3d.Meshes.SphereMesh3D(new Point3D(0, 0, 0), 80, 10); _rootMesh = sphere.Geometry; break; case 3: Ab3d.Meshes.SphereMesh3D sphere2 = new Ab3d.Meshes.SphereMesh3D(new Point3D(0, 0, 0), 80, 5); _rootMesh = sphere2.Geometry; break; case 4: Ab3d.Meshes.CylinderMesh3D cylinder = new Ab3d.Meshes.CylinderMesh3D(new Point3D(0, -50, 0), 60, 100, 12, true); _rootMesh = cylinder.Geometry; break; case 5: Ab3d.Meshes.ConeMesh3D cone = new Ab3d.Meshes.ConeMesh3D(new Point3D(0, -50, 0), 30, 60, 100, 12, true); _rootMesh = cone.Geometry; break; case 6: Ab3d.Meshes.ConeMesh3D cone2 = new Ab3d.Meshes.ConeMesh3D(new Point3D(0, -50, 0), 30, 60, 100, 6, false); _rootMesh = cone2.Geometry; break; case 7: var readerObj = new Ab3d.ReaderObj(); var teapotModel = readerObj.ReadModel3D(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Resources\ObjFiles\Teapot.obj")) as GeometryModel3D; if (teapotModel == null) { return; } // Get the teapot MeshGeometry3D _rootMesh = (MeshGeometry3D)teapotModel.Geometry; break; default: _rootMesh = null; break; } var geometryModel3D = new GeometryModel3D(_rootMesh, new DiffuseMaterial(Brushes.Silver)); MainViewport.Children.Clear(); MainViewport.Children.Add(geometryModel3D.CreateModelVisual3D()); MeshInspector.MeshGeometry3D = _rootMesh; }
// See also: // - Objects3D/BooleanOperationsSample to see other boolean operations // - Utilities/TextureCoordinatesGeneratorSample to see other options to define TextureCoordinates that are needed to show texture images public ModelingWithBooleanOperations() { InitializeComponent(); _orangeMaterial = new MaterialGroup(); _orangeMaterial.Children.Add(new DiffuseMaterial(Brushes.Orange)); _orangeMaterial.Children.Add(new SpecularMaterial(Brushes.White, 16)); // We will start with a simple box // Create it manually with MeshGeometry3D MeshGeometry3D initialBoxMesh3D = new Ab3d.Meshes.BoxMesh3D(new Point3D(-100, 25, 0), new Size3D(200, 50, 100), 1, 1, 1).Geometry; // Without boolean operations, we could simply create the box with: //var boxVisual3D = new BoxVisual3D() //{ // CenterPosition = new Point3D(-100, 25, 0), // Size = new Size3D(200, 50, 100), // UseCachedMeshGeometry3D = false, // we should not use the cached // Material = _orangeMaterial //}; // Define MeshGeometry3D objects that will be used for subtractions var centerSphereMesh = new Ab3d.Meshes.SphereMesh3D(new Point3D(-100, 50, 0), 40, 16).Geometry; var box1Mesh = new Ab3d.Meshes.BoxMesh3D(new Point3D(-180, 0, -30), new Size3D(10, 200, 10), 1, 1, 1).Geometry; var box2Mesh = new Ab3d.Meshes.BoxMesh3D(new Point3D(-180, 0, 0), new Size3D(10, 200, 10), 1, 1, 1).Geometry; var box3Mesh = new Ab3d.Meshes.BoxMesh3D(new Point3D(-180, 0, 30), new Size3D(10, 200, 10), 1, 1, 1).Geometry; var cylinder1Mesh3D = new Ab3d.Meshes.CylinderMesh3D(new Point3D(-20, 0, -30), 8, 200, segments: 16, isSmooth: false).Geometry; var cylinder2Mesh3D = new Ab3d.Meshes.CylinderMesh3D(new Point3D(-20, 0, 0), 8, 200, segments: 16, isSmooth: false).Geometry; var cylinder3Mesh3D = new Ab3d.Meshes.CylinderMesh3D(new Point3D(-20, 0, 30), 8, 200, segments: 16, isSmooth: false).Geometry; // When doing multiple boolean operations one after another, // it is recommended (and mush faster) to use BooleanMesh3D object. var booleanMesh3D = new Ab3d.Meshes.BooleanMesh3D(initialBoxMesh3D); booleanMesh3D.Subtract(centerSphereMesh); booleanMesh3D.Subtract(box1Mesh); booleanMesh3D.Subtract(box2Mesh); booleanMesh3D.Subtract(box3Mesh); booleanMesh3D.Subtract(cylinder1Mesh3D); booleanMesh3D.Subtract(cylinder2Mesh3D); booleanMesh3D.Subtract(cylinder3Mesh3D); // It is also possible to use Subtract method on BoxVisual3D object: //boxVisual3D.Subtract(centerSphereMesh); //boxVisual3D.Subtract(box1Mesh); //boxVisual3D.Subtract(box2Mesh); //boxVisual3D.Subtract(box3Mesh); //boxVisual3D.Subtract(cylinder1Mesh3D); //boxVisual3D.Subtract(cylinder2Mesh3D); //boxVisual3D.Subtract(cylinder3Mesh3D); // We could also use the static methods on Ab3d.Utilities.MeshBooleanOperations class: //Ab3d.Utilities.MeshBooleanOperations.Subtract(boxVisual3D, sphereMesh); //Ab3d.Utilities.MeshBooleanOperations.Subtract(boxVisual3D, boxMesh); // When calling the Geometry getter, the BooleanMesh3D converts the internal object into MeshGeometry3D. var meshGeometry3D = booleanMesh3D.Geometry; var initialGeometryModel3D = new GeometryModel3D(meshGeometry3D, _orangeMaterial); var boxVisual3D = initialGeometryModel3D.CreateModelVisual3D(); // Now add the changed box to the scene MainViewport.Children.Add(boxVisual3D); // When working with more complex meshes, the boolean operations can take some time. // To prevent blocking UI thread, we can do the boolean operations in the background thread. // To do this we need to freeze the MeshGeometry3D that are send to the background thread and back to UI thread. // // The following code is using spheres with 30 segments to demonstrate a longer boolean operations. // // First define the original MeshGeometry3D initialBoxMesh3D = new Ab3d.Meshes.BoxMesh3D(new Point3D(0, 25, 0), new Size3D(100, 50, 100), 1, 1, 1).Geometry; ShowNewOriginalMesh(initialBoxMesh3D); var initialBooleanMesh3D = new Ab3d.Meshes.BooleanMesh3D(initialBoxMesh3D); var sphereCenters = new Point3D[] { new Point3D(-50, 50, 50), new Point3D(50, 50, 50), new Point3D(50, 50, -50), new Point3D(-50, 50, -50), new Point3D(-50, 0, 50), new Point3D(50, 0, 50), new Point3D(50, 0, -50), new Point3D(-50, 0, -50) }; // We can create the MeshGeometry3D and prepare the BooleanMesh3D in parallel - each can be created in its own thread var sphereBooleanMeshes = sphereCenters.AsParallel().Select(sphereCenter => { //System.Diagnostics.Debug.WriteLine($"Start creating SphereMesh3D with {sphereCenter} on {System.Threading.Thread.CurrentThread.ManagedThreadId}"); var sphereMeshGeometry3D = new Ab3d.Meshes.SphereMesh3D(sphereCenter, radius: 15, segments: 30).Geometry; var sphereBooleanMesh3D = new Ab3d.Meshes.BooleanMesh3D(sphereMeshGeometry3D); return(sphereBooleanMesh3D); }); // After that we can subtract each of the sphere meshes from the initialBooleanMesh3D. // This can be done in background thread (we use Dispatcher.Invoke to "send" the mesh to the UI thread) Task.Factory.StartNew(() => { // Note the order in which the sphereBooleanMesh are created is not determined (as the BooleanMesh3D are created, they get available to the foreach) foreach (var sphereBooleanMesh in sphereBooleanMeshes) { // Subtract the sphereBooleanMesh from the initialBooleanMesh3D initialBooleanMesh3D.Subtract(sphereBooleanMesh); // Get the MeshGeometry3D var mesh = initialBooleanMesh3D.Geometry; // Freeze the MeshGeometry3D // This will allow using the mesh in UI thread (different thread as the mesh was created) mesh.Freeze(); // Send it to UI thread this.Dispatcher.BeginInvoke(new Action(() => { ShowNewOriginalMesh(mesh); })); } }) // After all the boolean operations are completed, we generate texture coordinates // so we will be able to use texture image as material (meshes that are created with boolean operations do not have texture coordinates defined) .ContinueWith(_ => GenerateTextureCoordinates(), TaskScheduler.FromCurrentSynchronizationContext()); // The last one is run on UI thread }