Пример #1
0
        private void AddHouseWithTreesModel()
        {
            // Use assimp importer to load house with trees.3DS
            AssimpLoader.LoadAssimpNativeLibrary();

            var assimpWpfImporter   = new AssimpWpfImporter();
            var houseWithTreesModel = (Model3DGroup)assimpWpfImporter.ReadModel3D(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Resources\Models\house with trees.3DS"));

            // Add AmbientLight
            var ambientLight = new AmbientLight(Color.FromRgb(80, 80, 80));

            houseWithTreesModel.Children.Add(ambientLight);

            // Show the loaded 3d scene
            var modelVisual3D = houseWithTreesModel.CreateModelVisual3D();

            _masterDXViewportView.Viewport3D.Children.Add(modelVisual3D);

            // Save the man01 model for animation (when clicked on "Change scene" button)
            _animatedPersonModel = assimpWpfImporter.NamedObjects["man01"] as Model3DGroup;


            // Add base green plate and the house models to a CustomRenderingQueue.
            // This is done with setting value of CustomRenderingQueue (custom DXAttribute) on some parts of the 3D scene.
            // This will add the specified models to the custom rendering queue (created in the OnMasterDXSceneCreated method).
            var modelNames = new string[] { "Box01", "Box02", "roof01" };

            foreach (var modelName in modelNames)
            {
                var model3D = assimpWpfImporter.NamedObjects[modelName] as Model3D;

                // Note that CustomRenderingQueue can be set to an instance of RenderingQueue or to a name (as string) of the RenderingQueue
                model3D.SetDXAttribute(DXAttributeType.CustomRenderingQueue, "CustomRenderingQueue");
            }
        }
        private void LoadModel(string fileName)
        {
            MainViewport.Children.Clear();

            // Create an instance of AssimpWpfImporter
            var assimpWpfImporter = new AssimpWpfImporter();
            var readModel3D       = assimpWpfImporter.ReadModel3D(fileName, texturesPath: null); // we can also define a textures path if the textures are located in some other directory (this is parameter can be skipped, but is defined here so you will know that you can use it)


            if (readModel3D == null)
            {
                MessageBox.Show("Cannot read file");
                return;
            }

            MainViewport.Children.Add(readModel3D.CreateModelVisual3D());


            if (_fileName != fileName) // Reset camera only when the file is loaded for the first time
            {
                _fileName = fileName;

                Camera1.TargetPosition = readModel3D.Bounds.GetCenterPosition();
                Camera1.Distance       = readModel3D.Bounds.GetDiagonalLength() * 1.2;
            }

            // Add ambient light
            var ambientLight = new AmbientLight(Color.FromRgb(100, 100, 100));

            MainViewport.Children.Add(ambientLight.CreateModelVisual3D());
        }
        public AssimpWpfImporterSample()
        {
            InitializeComponent();


            // Use helper class (defined in this sample project) to load the native assimp libraries.
            // IMPORTANT: See commend in the AssimpLoader class for details on how to prepare your project to use assimp library.
            AssimpLoader.LoadAssimpNativeLibrary();


            var assimpWpfImporter = new AssimpWpfImporter();

            string[] supportedImportFormats = assimpWpfImporter.SupportedImportFormats;

            var assimpWpfExporter = new AssimpWpfExporter();

            string[] supportedExportFormats = assimpWpfExporter.ExportFormatDescriptions.Select(f => f.FileExtension).ToArray();

            FileFormatsTextBlock.Text = string.Format("Using native Assimp library version {0}.\r\n\r\nSupported import formats:\r\n{1}\r\n\r\nSupported export formats:\r\n{2}",
                                                      assimpWpfImporter.AssimpVersion,
                                                      string.Join(", ", supportedImportFormats),
                                                      string.Join(", ", supportedExportFormats));


            var dragAndDropHelper = new DragAndDropHelper(this, ".*");

            dragAndDropHelper.FileDropped += (sender, args) => LoadModel(args.FileName);


            string startUpFileName = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Resources\Collada\duck.dae");

            LoadModel(startUpFileName);
        }
Пример #4
0
        public PropertiesView()
        {
            InitializeComponent();

            Helpers.LoadAssimpNativeLibrary();

            var assimpWpfImporter = new AssimpWpfImporter();

            string[] supportedImportFormats = assimpWpfImporter.SupportedImportFormats;
            var      assimpWpfExporter      = new AssimpWpfExporter();

            string[] supportedExportFormats = assimpWpfExporter.ExportFormatDescriptions.Select(f => f.FileExtension).ToArray();

            StaticReferences.GlobalPropertiesView = this;

            var themeResources = Application.LoadComponent(new Uri("Resources/Styles/ExpressionDark.xaml", UriKind.Relative)) as ResourceDictionary;

            Resources.MergedDictionaries.Add(themeResources);

            spectrumAnalyzer.RegisterSoundPlayer(NAudioSimpleEngine.Instance);
            waveformTimeline.RegisterSoundPlayer(NAudioSimpleEngine.Instance);

            //appControl.ExeName = "binkpl64.exe";
            //appControl.Args = "test2.bk2 /J /I2 /P";
            //this.Unloaded += new RoutedEventHandler((s, e) => { appControl.Dispose(); });
        }
Пример #5
0
        private Model3D LoadDuckModel()
        {
            string fileName = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Resources\Models\duck.dae");

            var assimpWpfImporter = new AssimpWpfImporter();
            var readModel3D       = assimpWpfImporter.ReadModel3D(fileName, texturesPath: null); // we can also define a textures path if the textures are located in some other directory (this is parameter can be skipped, but is defined here so you will know that you can use it)

            // Move the model so that it has its bottom center position at (0,0,0) and
            // scale it so that its height is set to DuckSize (0.3m) - multiply xSize and zSize by 10 so that only y size will limit the final size (we preserve the aspect ratio)
            Ab3d.Utilities.ModelUtils.PositionAndScaleModel3D(readModel3D, new Point3D(0, 0, 0), PositionTypes.Bottom, new Size3D(DuckSize * 10, DuckSize, DuckSize * 10), preserveAspectRatio: true, preserveCurrentTransformation: false);

            return(readModel3D);
        }
        // NOTE: We need assimpWpfImporter because of GetGeometryModel3DForAssimpMesh method

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="assimpWpfImporter">AssimpWpfImporter that was used to read the Assimp object (needed because of GetGeometryModel3DForAssimpMesh method)</param>
        public AssimpAnimationController(AssimpWpfImporter assimpWpfImporter)
        {
            if (assimpWpfImporter == null)
            {
                throw new ArgumentNullException(nameof(assimpWpfImporter));
            }

            _assimpWpfImporter = assimpWpfImporter;

            _assimpScene = assimpWpfImporter.ImportedAssimpScene;

            if (_assimpScene == null)
            {
                throw new Exception("AssimpScene not yet read with the specified AssimpWpfImporter");
            }


            Skeletons = new List <Skeleton>();


            if (!_assimpScene.HasAnimations)
            {
                return;
            }

            foreach (var assimpMesh in _assimpScene.Meshes)
            {
                if (!assimpMesh.HasBones)
                {
                    continue;
                }

                // If we are here then this mesh has Bones
                var geometryModel3D = assimpWpfImporter.GetGeometryModel3DForAssimpMesh(assimpMesh);

                if (geometryModel3D == null)
                {
                    continue;
                }

                var wpfMeshGeometry3D = (MeshGeometry3D)geometryModel3D.Geometry;
                var skeleton          = new Skeleton(assimpMesh, wpfMeshGeometry3D, _assimpScene);

                geometryModel3D.Transform = null; // We need to remove the transformation of the whole GeometryModel3D because we will transform individual positions instead

                Skeletons.Add(skeleton);
            }

            // By default select the first animation
            SelectAnimation(_assimpScene.Animations[0]);
        }
        public PropertiesView()
        {
            InitializeComponent();

            ViewModel   = Locator.Current.GetService <PropertiesViewModel>();
            DataContext = ViewModel;

            AssimpHelper.LoadAssimpNativeLibrary();

            var assimpWpfImporter      = new AssimpWpfImporter();
            var supportedImportFormats = assimpWpfImporter.SupportedImportFormats;
            var assimpWpfExporter      = new AssimpWpfExporter();
            var supportedExportFormats = assimpWpfExporter.ExportFormatDescriptions.Select(f => f.FileExtension).ToArray();

            //var themeResources = Application.LoadComponent(new Uri("Resources/Styles/ExpressionDark.xaml", UriKind.Relative)) as ResourceDictionary;
            //Resources.MergedDictionaries.Add(themeResources);

            spectrumAnalyzer.RegisterSoundPlayer(NAudioSimpleEngine.Instance);
            waveformTimeline.RegisterSoundPlayer(NAudioSimpleEngine.Instance);

            nAudioSimple = NAudioSimpleEngine.Instance;
            NAudioSimpleEngine.Instance.PropertyChanged += NAudioEngine_PropertyChanged;

            //appControl.ExeName = "binkpl64.exe";
            //appControl.Args = "test2.bk2 /J /I2 /P";
            //this.Unloaded += new RoutedEventHandler((s, e) => { appControl.Dispose(); });

            this.WhenActivated(disposables =>
            {
                ViewModel.WhenAnyValue(x => x.LoadedBitmapFrame).Subscribe(source =>
                {
                    if (source is { } frame)
                    {
                        LoadImage(frame);
                    }
                });
                ViewModel.WhenAnyValue(x => x.LoadedModelPath).Subscribe(source =>
                {
                    if (source is { } modelpath)
                    {
                        LoadModel(modelpath);
                    }
                });

                ViewModel.PreviewAudioCommand.Subscribe(path =>
                {
                    TempConvertToWemWav(path);
                });
            });
        }
        public AssimpIntroPage()
        {
            InitializeComponent();

            // We create an instance of AssimpWpfImporter to read all supported file formats
            // and set that to the FileFormatsTextBlock

            // Use helper class (defined in this sample project) to load the native assimp libraries
            // IMPORTANT: See commend in the AssimpLoader class for details on how to prepare your project to use assimp library.
            AssimpLoader.LoadAssimpNativeLibrary();

            var assimpWpfImporter = new AssimpWpfImporter();

            string[] supportedImportFormats = assimpWpfImporter.SupportedImportFormats;
            FileFormatsTextBlock.Text = string.Join(", ", supportedImportFormats);
        }
        private Model3D LoadModel3D(string fileName, string customTexturesFolder, out Dictionary <string, Model3D> namedObjects)
        {
            if (!System.IO.File.Exists(fileName))
            {
                MessageBox.Show($"File does not exist:\r\n{fileName}");
                namedObjects = null;

                return(null);
            }


            // Therefore we need to use Assimp importer
            var assimpWpfImporter = new AssimpWpfImporter();

            // Let assimp calculate the tangents that are needed for normal mapping
            assimpWpfImporter.AssimpPostProcessSteps = PostProcessSteps.CalculateTangentSpace;

            var assimpScene = assimpWpfImporter.ReadFileToAssimpScene(fileName);

            var assimpWpfConverter = new AssimpWpfConverter();

            assimpWpfConverter.RemoveEmptyModel3DGroups = false; // Prevent removing "Helper_Extras" Model3DGroup - placholder that is used for tools

            var readModel3D = assimpWpfConverter.ConvertAssimpModel(assimpScene, System.IO.Path.GetDirectoryName(fileName));


            // Update WPF materials with PhysicallyBasedMaterial
            UpdateMaterials(fileName, customTexturesFolder, assimpScene, assimpWpfConverter, useStrictFileNameMatch: true, supportDDSTextures: true);


            // Convert ObjectNames to NamedObjects
            namedObjects = new Dictionary <string, Model3D>(assimpWpfConverter.ObjectNames.Count);
            foreach (var keyValuePair in assimpWpfConverter.ObjectNames)
            {
                var model3D = keyValuePair.Key as Model3D;

                if (model3D == null || keyValuePair.Value == null)
                {
                    continue;
                }

                namedObjects[keyValuePair.Value] = model3D;
            }


            return(readModel3D);
        }
        private Model3D LoadModel3D()
        {
            AssimpLoader.LoadAssimpNativeLibrary();

            string fileName = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Resources\Models\robotarm-upper-part.3ds");

            var assimpWpfImporter = new AssimpWpfImporter();
            var robotModel3D      = assimpWpfImporter.ReadModel3D(fileName);

            // To get object names execute the following in Visual Studio Immediate window:
            // robotModel3D.DumpHierarchy();

            var hand2Group = assimpWpfImporter.NamedObjects["Hand2__Group"] as Model3DGroup;

            _originalModelAxisAngleRotation3D = new AxisAngleRotation3D(new Vector3D(0, 0, -1), 0);
            var rotateTransform3D = new RotateTransform3D(_originalModelAxisAngleRotation3D);

            Ab3d.Utilities.TransformationsHelper.AddTransformation(hand2Group, rotateTransform3D);


            // By default all objects cast shadow, so to prevent that we need to set the IsCastingShadow to false.
            // This can be done in two ways:
            //
            // 1) Set DXAttributeType.IsCastingShadow on GeometryModel3D
            //    (only GeometryModel3D support that attribute;
            //     this need to be set before the WpfGeometryModel3DNode object is created from GeometryModel3D)
            //
            // 2) After the WpfGeometryModel3DNode is created from GeometryModel3D,
            // then we can set the IsCastingShadow on that SceneNode object - see commented code below.
            //
            // When we change the value of IsCastingShadow, we also need to enable checking this property with
            // setting the PlanarShadowRenderingProvider.IsCheckingIsCastingShadow property to true.
            // By default this property is set to false so improve performance
            // (prevent using FilterObjectsFunction in RenderObjectsRenderingStep that render shadow).

            _teapotGeometryModel3D = assimpWpfImporter.NamedObjects["Teapot"] as GeometryModel3D;
            _teapotGeometryModel3D.SetDXAttribute(DXAttributeType.IsCastingShadow, false);

            // If we want to change the value of IsCastingShadow when the object is already shown,
            // we need to change that in the WpfGeometryModel3DNode - for example:
            //var teapotSceneNode = MainDXViewportView.GetSceneNodeForWpfObject(_teapotGeometryModel3D) as WpfGeometryModel3DNode; // we can get the teapotSceneNode in MainDXViewportView.DXSceneInitialized
            //if (teapotSceneNode != null)
            //    teapotSceneNode.IsCastingShadow = false;

            return(robotModel3D);
        }
Пример #11
0
        private void LoadModel(string fileName)
        {
            // Create an instance of AssimpWpfImporter
            var assimpWpfImporter = new AssimpWpfImporter();

            string fileExtension = System.IO.Path.GetExtension(fileName);

            if (!assimpWpfImporter.IsImportFormatSupported(fileExtension))
            {
                MessageBox.Show("Assimp does not support importing files file extension: " + fileExtension);
                return;
            }

            try
            {
                Mouse.OverrideCursor = Cursors.Wait;

                // Before reading the file we can set the default material (used when no other material is defined - here we set the default value again)
                assimpWpfImporter.DefaultMaterial = new DiffuseMaterial(Brushes.Silver);

                // After assimp importer reads the file, it can execute many post processing steps to transform the geometry.
                // See the possible enum values to see what post processes are available.
                // By default we just execute the Triangulate step to convert all polygons to triangles that are needed for WPF 3D.
                assimpWpfImporter.AssimpPostProcessSteps = PostProcessSteps.Triangulate;

                // Read model from file
                Model3D readModel3D = assimpWpfImporter.ReadModel3D(fileName, texturesPath: null); // we can also define a textures path if the textures are located in some other directory (this is parameter can be skipped, but is defined here so you will know that you can use it)

                // Save the names of the objects - the same dictionary can be used when exporting the objects
                _namedObjects = assimpWpfImporter.NamedObjects;

                // Show the model
                ShowModel(readModel3D);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error reading file:\r\n" + ex.Message);
            }
            finally
            {
                // Dispose unmanaged resources
                assimpWpfImporter.Dispose();

                Mouse.OverrideCursor = null;
            }
        }
        public AssimpWpfExporterSample()
        {
            InitializeComponent();

            AssimpWpfImporter.LoadAssimpNativeLibrary(AppDomain.CurrentDomain.BaseDirectory);


            var assimpWpfExporter = new AssimpWpfExporter();

            _exportFormatDescriptions = assimpWpfExporter.ExportFormatDescriptions;


            for (int i = 0; i < _exportFormatDescriptions.Length; i++)
            {
                var comboBoxItem = new ComboBoxItem()
                {
                    Content = string.Format("{0} (.{1})", _exportFormatDescriptions[i].Description, _exportFormatDescriptions[i].FileExtension),
                    Tag     = _exportFormatDescriptions[i].FormatId
                };

                ExportTypeComboBox.Items.Add(comboBoxItem);
            }


            ExportTypeComboBox.SelectedIndex = 0; // Use Collada file format by default
            _selectedExportFormatId          = _exportFormatDescriptions[ExportTypeComboBox.SelectedIndex].FormatId;


            // Use helper class (defined in this sample project) to load the native assimp libraries
            // IMPORTANT: See commend in the AssimpLoader class for details on how to prepare your project to use assimp library.
            AssimpLoader.LoadAssimpNativeLibrary();

            _assimpWpfImporter = new AssimpWpfImporter();
            _assimpWpfImporter.AssimpPostProcessSteps = PostProcessSteps.Triangulate;

            CreateTestScene();

            // Set initial output file name
            OutputFileName.Text = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "AssimpExport.dae");

            // Add drag and drop handler for all file extensions
            var dragAndDropHelper = new DragAndDropHelper(ViewportBorder, "*");

            dragAndDropHelper.FileDroped += (sender, e) => LoadModel(e.FileName);
        }
        private Model3D LoadModel()
        {
            AssimpLoader.LoadAssimpNativeLibrary();

            string fileName = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Resources\robotarm-upper-part.3ds");

            var assimpWpfImporter = new AssimpWpfImporter();
            var robotModel3D      = assimpWpfImporter.ReadModel3D(fileName);

            var hand2Group = assimpWpfImporter.NamedObjects["Hand2__Group"] as Model3DGroup;

            _axisAngleRotation3D = new AxisAngleRotation3D(new Vector3D(0, 0, -1), 0);
            var rotateTransform3D = new RotateTransform3D(_axisAngleRotation3D);

            Ab3d.Utilities.TransformationsHelper.AddTransformation(hand2Group, rotateTransform3D);

            return(robotModel3D);
        }
        private void LoadExportedScene(string fileName)
        {
            // Now read the exported file and show in the right Viewport3D

            Model3D readModel3D;

            try
            {
                // With uncommenting the following few lines we would use Ab3d.ReaderObj from Ab3d.PowerToys to read obj files instead of Assimp
                //if (fileName.EndsWith(".obj", ignoreCase: true, culture: CultureInfo.InvariantCulture))
                //{
                //    var readerObj = new Ab3d.ReaderObj();
                //    readModel3D = readerObj.ReadModel3D(fileName);
                //}
                //else
                //{
                var assimpWpfImporter = new AssimpWpfImporter();
                readModel3D = assimpWpfImporter.ReadModel3D(fileName);
                //}
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error reading file:\r\n" + ex.Message);
                return;
            }


            var modelVisual3D = new ModelVisual3D();

            modelVisual3D.Content = readModel3D;

            MainViewport2.Children.Clear();
            MainViewport2.Children.Add(modelVisual3D);

            // Set Camera2 from Camera1
            Camera2.TargetPosition = Camera1.TargetPosition;
            Camera2.Heading        = Camera1.Heading;
            Camera2.Attitude       = Camera1.Attitude;
            Camera2.Distance       = Camera1.Distance;

            Camera2.Refresh(); // This will regenerate light that was cleared with MainViewport2.Children.Clear()

            ExportedSceneTitleTextBlock.Text = "Scene imported from " + fileName;
        }
Пример #15
0
        // load assimp libraries.
        public static void LoadAssimpNativeLibrary()
        {
            try
            {
                var assimp32Folder = AppDomain.CurrentDomain.BaseDirectory;
                var assimp64Folder = assimp32Folder;
                AssimpWpfImporter.LoadAssimpNativeLibrary(assimp32Folder, assimp64Folder);
            }
            catch (AssimpException ex)
            {
                MessageBox.Show(
                    @"Error loading native assimp library!

                The most common cause of this error is that the 
                Visual C++ Redistributable for Visual Studio 2015
                is not installed on the system. 

                Please install it manually or contact support of the application.

                Error message: " + ex.Message);
                throw;
            }
        }
Пример #16
0
        private void LoadRobotArm()
        {
            string fileName = AppDomain.CurrentDomain.BaseDirectory + @"Resources\robotarm.3ds";

            // Create an instance of AssimpWpfImporter
            var assimpWpfImporter = new AssimpWpfImporter();

            _robotArmModel3D = assimpWpfImporter.ReadModel3D(fileName, texturesPath: null);

            // In VS Immediate window call "readModel3D.DumpHierarchy()" to get hierarchy and names of the objects
            var baseModel3D   = assimpWpfImporter.NamedObjects["Base__Group"] as Model3D;
            var joint2Model3D = assimpWpfImporter.NamedObjects["Joint2__Group"] as Model3D;

            _baseAxisAngleRotation3D   = new AxisAngleRotation3D(new Vector3D(0, 0, 1), 0);
            _joint2AxisAngleRotation3D = new AxisAngleRotation3D(new Vector3D(0, 0, 1), 0);

            // Add RotateTransform3D to existing transformations
            var transform3DGroup = new Transform3DGroup();

            transform3DGroup.Children.Add(new RotateTransform3D(_baseAxisAngleRotation3D));
            transform3DGroup.Children.Add(baseModel3D.Transform);

            baseModel3D.Transform = transform3DGroup;

            transform3DGroup = new Transform3DGroup();
            transform3DGroup.Children.Add(new RotateTransform3D(_joint2AxisAngleRotation3D));
            transform3DGroup.Children.Add(joint2Model3D.Transform);

            joint2Model3D.Transform = transform3DGroup;

            ModelRootVisual3D.Children.Add(_robotArmModel3D.CreateModelVisual3D());


            // NOTES:
            // 1)
            // The source code for EdgeLinesFactory class is written in a comment at the end of the StaticEdgeLinesCreationSample.xaml.cs file.
            // You can change its source and create a new class that would suite your needs.
            //
            // 2)
            // Call CreateEdgeLinesForEachGeometryModel3D that will generate MultiLineVisual3D
            // with edge lines for each GeometryModel3D in the _robotArmModel3D hierarchy.
            // The MultiLineVisual3D are added to to the EdgeLinesRootVisual3D.
            // The create MultiLineVisual3D will be set as EdgeLinesFactory.EdgeMultiLineVisual3DProperty to each GeometryModel3D.
            // This way we will be able to update the Transformation in the MultiLineVisual3D after the transformation in the model is changed.
            // This is done in the OnRendering method.
            //
            // If the model is not animated or otherwise transformed, then it is recommended to call AddEdgeLinePositions
            // that will create static edge lines. See StaticEdgeLinesCreationSample for more info.
            //
            // 3)
            // Multi-threading:
            // You cannot call CreateEdgeLinesForEachGeometryModel3D in background treads because MultiLineVisual3D object cannot be used on other threads.
            // But you can move most of the work (generating edge line positions) to the background threads.
            // To do that you will need to manually collect all MeshGeometry3D objects, call Freeze method on them and then in multiple threads
            // create MeshAnalyzer classes and call CreateEdgeLines for each MeshGeometry3D.
            // Because you will not be able to store the generated lines into MeshGeometry3D (it is frozen), you will need to crate a Dictionary
            // where key will be MeshGeometry3D and value will be the List<int> that is created by the CreateEdgeLines method.
            // After all the threads have completed its work, then in the main thread you can create MultiLineVisual3D objects
            // based on the data from the Dictionary that was created in each thread.
            EdgeLinesFactory.CreateEdgeLinesForEachGeometryModel3D(_robotArmModel3D, edgeStartAngleInDegrees: 25, lineThickness: 2, lineColor: Colors.Black, parentModelVisual3D: EdgeLinesRootVisual3D);

            SetupAnimation();
        }
        private void LoadFileWithSkinnedAnimation(string fileName)
        {
            InfoTextBox.Text = "";

            // Create an instance of AssimpWpfImporter
            var assimpWpfImporter = new AssimpWpfImporter();


            Model3D readModel3D;

            try
            {
                readModel3D = assimpWpfImporter.ReadModel3D(fileName, texturesPath: null);  // we can also define a textures path if the textures are located in some other directory (this is parameter can be skipped, but is defined here so you will know that you can use it)
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("Error reading file:\r\n{0}\r\n\r\n{1}", fileName, ex.Message));
                return;
            }

            _lastLoadedModel3D = readModel3D;

            ModelSilhueteVisual3D.Children.Clear();
            ModelSilhueteVisual3D.Children.Add(readModel3D.CreateModelVisual3D());

            // Set camera to show the whole model
            Camera1.TargetPosition = readModel3D.Bounds.GetCenterPosition();
            Camera1.Distance       = readModel3D.Bounds.GetDiagonalLength() * 1.2;


            BoneMarkersVisual3D.Children.Clear();

            // Stop current animation if it was running
            if (_assimpAnimationController != null)
            {
                _assimpAnimationController.StopAnimation();
                _assimpAnimationController = null;
            }


            _assimpScene = assimpWpfImporter.ImportedAssimpScene;

            if (_assimpScene.AnimationCount == 0)
            {
                // No animation in the file
                AnimationSelectionComboBox.IsEnabled     = false;
                AnimationSelectionComboBox.ItemsSource   = new string[] { "(no animation defined)" };
                AnimationSelectionComboBox.SelectedIndex = 0;

                ShowBonesCheckBox.IsEnabled = false;
                ShowBonesCheckBox.IsChecked = false;

                AnimationSlider.IsEnabled = false;

                StartStopAnimationButton.IsEnabled = false;

                UpdateAnimationUI(0);

                return;
            }

            AnimationSlider.IsEnabled          = true;
            StartStopAnimationButton.IsEnabled = true;


            try
            {
                // Create AssimpAnimationController - it will play the keyframe and skeletal animation
                _assimpAnimationController             = new AssimpAnimationController(assimpWpfImporter);
                _assimpAnimationController.AutoReverse = false;
                _assimpAnimationController.AutoRepeat  = true;

                _assimpAnimationController.AfterFrameUpdated += OnAssimpAnimationControllerOnAfterFrameUpdated;


                SetupAnimationUI();
                UpdateBoneMarkersAndUI();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error starting animation:\r\n" + ex.Message);
                return;
            }

            // Setup animation names
            var animationNames = _assimpScene.Animations.Select(a => a.Name).ToList();

            AnimationSelectionComboBox.IsEnabled   = true;
            AnimationSelectionComboBox.ItemsSource = animationNames;

            int startupIndex = animationNames.IndexOf("Run"); // Start with "Run" animation if it exists

            if (startupIndex < 0)
            {
                startupIndex = 0;
            }

            AnimationSelectionComboBox.SelectedIndex = startupIndex; // This will call ChangeAnimationName method


            if (_assimpAnimationController.HasSkeletalAnimation)
            {
                ShowBonesCheckBox.IsEnabled = true;
                ShowBonesCheckBox.ToolTip   = null;

                // In case ShowBonesCheckBox is checked then set model opacity to 0.8
                UpdateModelOpacity();
            }
            else
            {
                // No skeletal animation (probably only keyframe animation)
                ShowBonesCheckBox.IsEnabled = false;
                ShowBonesCheckBox.IsChecked = false;

                ShowBonesCheckBox.ToolTip = "This files does not define any skeletal animation.";
            }

            //StartAnimation();
        }
        private void LoadModel(string fileName)
        {
            bool isNewFile = false;

            // Create an instance of AssimpWpfImporter
            var assimpWpfImporter = new AssimpWpfImporter();

            string fileExtension = System.IO.Path.GetExtension(fileName);

            if (!assimpWpfImporter.IsImportFormatSupported(fileExtension))
            {
                MessageBox.Show("Assimp does not support importing files file extension: " + fileExtension);
                return;
            }

            try
            {
                Mouse.OverrideCursor = Cursors.Wait;

                // Before readin the file we can set the default material (used when no other materila is defined - here we set the default value again)
                assimpWpfImporter.DefaultMaterial = new DiffuseMaterial(Brushes.Silver);

                // After assimp importer reads the file, it can execute many post processing steps to transform the geometry.
                // See the possible enum values to see what post processes are available.
                // Here we just set the AssimpPostProcessSteps to its default value - execute the Triangulate step to convert all polygons to triangles that are needed for WPF 3D.
                // Note that if ReadPolygonIndices is set to true in the next line, then the assimpWpfImporter will not use assimp's triangulation because it needs original polygon data.
                assimpWpfImporter.AssimpPostProcessSteps = PostProcessSteps.Triangulate;

                // When ReadPolygonIndices is true, assimpWpfImporter will read PolygonIndices collection that can be used to show polygons instead of triangles.
                assimpWpfImporter.ReadPolygonIndices = ReadPolygonIndicesCheckBox.IsChecked ?? false;

                // Read model from file
                Model3D readModel3D;

                try
                {
                    readModel3D = assimpWpfImporter.ReadModel3D(fileName, texturesPath: null); // we can also define a textures path if the textures are located in some other directory (this is parameter can be skipped, but is defined here so you will know that you can use it)

                    // To read 3D model from stream, use the following code:
                    //var extension = System.IO.Path.GetExtension(fileName); // extension is needed as a format hint so assimp will know which importer to use
                    //using (var fileStream = System.IO.File.OpenRead(fileName))
                    //{
                    //    readModel3D = assimpWpfImporter.ReadModel3D(fileStream, extension, resolveResourceFunc: null); // when reading models with texture, you will need to define the resolveResourceFunc
                    //}

                    isNewFile = (_fileName != fileName);
                    _fileName = fileName;
                }
                catch (Exception ex)
                {
                    readModel3D = null;
                    MessageBox.Show("Error importing file:\r\n" + ex.Message);
                }

                // After the model is read and if the object names are defined in the file,
                // you can get the model names by assimpWpfImporter.ObjectNames
                // or get object by name with assimpWpfImporter.NamedObjects

                // To get the  object model of the assimp importer, you can observe the assimpWpfImporter.ImportedAssimpScene

                // Show the model
                ShowModel(readModel3D, updateCamera: isNewFile); // If we just reloaded the previous file, we preserve the current camera TargetPosition and Distance
            }
            finally
            {
                // Dispose unmanaged resources
                assimpWpfImporter.Dispose();

                Mouse.OverrideCursor = null;
            }
        }
Пример #19
0
        public BasicWpf3dObjectsTutorial()
        {
            InitializeComponent();

            var mesh2 = new MeshGeometry3D()
            {
                Positions = new Point3DCollection(new[]
                {
                    new Point3D(5, 0, 5),
                    new Point3D(100, 0, 5),
                    new Point3D(100, 0, 50),
                    new Point3D(5, 0, 50)
                }),

                TriangleIndices = new Int32Collection(new[]
                {
                    0, 2, 1,
                    3, 2, 0
                })
            };

            var geometryModel2 = new GeometryModel3D()
            {
                Geometry     = mesh2,
                Material     = new DiffuseMaterial(Brushes.LightGreen),
                BackMaterial = new DiffuseMaterial(Brushes.Red),
            };

            var modelVisual2 = new ModelVisual3D()
            {
                Content = geometryModel2
            };

            //// Using CreateModelVisual3D extensiton method from Ab3d.PowerToys
            //var modelVisual2 = geometryModel2.CreateModelVisual3D();
            //Viewport2.Children.Add(modelVisual2);

            //// in one line:
            //Viewport2.Children.Add(geometryModel2.CreateModelVisual3D());

            Viewport2.Children.Add(modelVisual2);

            MeshInspector2.MeshGeometry3D = mesh2;

            // #############

            var mesh3 = new MeshGeometry3D()
            {
                Positions       = mesh2.Positions,
                TriangleIndices = new Int32Collection(new[]
                {
                    2, 0, 1, // changed from 0, 2, 1
                    3, 2, 0
                }),
                Normals = new Vector3DCollection(new [] { new Vector3D(0, 1, 0), new Vector3D(0, 1, 0), new Vector3D(0, 1, 0), new Vector3D(0, 1, 0) }) // We define Normals because the automatically generated normals are not correct because the two triangles are oriented differently and this produces zero length normals for the shared positions; note that for mesh2 this was not needed because triangles are correctly oriented and WPF was able to correctly calculate normals.
            };

            _geometryModel3 = new GeometryModel3D()
            {
                Geometry     = mesh3,
                Material     = new DiffuseMaterial(Brushes.LightGreen),
                BackMaterial = new DiffuseMaterial(Brushes.Red),
            };

            var modelVisual3 = new ModelVisual3D()
            {
                Content = _geometryModel3
            };

            Viewport3.Children.Add(modelVisual3);

            MeshInspector3.MeshGeometry3D = mesh3;

            // #############

            var modelVisual4 = new ModelVisual3D()
            {
                Content = _geometryModel3
            };

            Viewport4.Children.Add(modelVisual4);

            MeshInspector4.MeshGeometry3D = mesh3;

            // #############

            var mesh5 = new MeshGeometry3D()
            {
                Positions          = mesh2.Positions,
                TriangleIndices    = mesh2.TriangleIndices,
                TextureCoordinates = new PointCollection(new[]
                {
                    new Point(0, 0),
                    new Point(1, 0),
                    new Point(1, 1),
                    new Point(0, 1),
                })
            };

            var textureImage = new BitmapImage(new Uri("pack://*****:*****@"c:\images\texture.png")); // Read image from file
            //var textureImage2 = new BitmapImage(new Uri("pack://*****:*****@"Resources\robotarm-upper-part.3ds");

            var assimpWpfImporter = new AssimpWpfImporter();
            var robotModel3D      = assimpWpfImporter.ReadModel3D(fileName);

            string dumpString = Ab3d.Utilities.Dumper.GetObjectHierarchyString(robotModel3D);

            RobotArmSampleTextBox.Text = dumpString;


            //var transform3DGroup = new Transform3DGroup();
            //transform3DGroup.Children.Add(new ScaleTransform3D(2, 3, 2));
            //transform3DGroup.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1, 0), 45)));
            //transform3DGroup.Children.Add(new TranslateTransform3D(100, 0, 0));

            //geometryModel2.Transform = transform3DGroup;
        }
        private void LoadModelWithEdgeLines(string fileName)
        {
            MainViewport.Children.Clear();

            // Create an instance of AssimpWpfImporter
            var assimpWpfImporter = new AssimpWpfImporter();
            var readModel3D       = assimpWpfImporter.ReadModel3D(fileName, texturesPath: null); // we can also define a textures path if the textures are located in some other directory (this is parameter can be skipped, but is defined here so you will know that you can use it)

            MainViewport.Children.Add(readModel3D.CreateModelVisual3D());


            // NOTES:
            // 1)
            // The source code for EdgeLinesFactory class is written below (in a comment at the end of this file).
            // You can change its source and create a new class that would suite your needs.
            //
            // 2)
            // With using AddEdgeLinePositions we will create STATIC lines from the current readModel3D.
            // If the readModel3D would be changed (any child transformation would be changed),
            // then the lines would not be correct any more.
            // See the DynamicEdgeLinesSample to see how to create dynamic edge lines.
            // If your object will not change, then it is better to create static edge lines for performance reasons
            // (having single MultiLineVisual3D for the whole instead of one MultiLineVisual3D for each GeometryModel3D).
            //
            // 3)
            // Multi-threading:
            // If you want to call AddEdgeLinePositions method on background threads, you will need to call Freeze on the readModel3D
            // (this will make the model and meshes immutable and this way they can be read on other threads).
            // You will also need to create Point3DCollection for each thread and after it is filled in AddEdgeLinePositions
            // call Freeze on it so it can be "send" to the main UI thread.
            // Another option (better) is to create a MeshAnalyzer class in each thread and for each MeshGeometry3D call CreateEdgeLines
            // (see source of the EdgeLinesFactory.GenerateEdgeLineIndices method).
            //
            // 4)
            // You can use wpf3d file format to save 3D models with embedded EdgeLineIndices data (and also with PolygonIndices data).
            // The source code for wpf3d file format is available in this project under Wpf3DFile folder (see sample in this folder for more info).
            // This way you can calculate model edges in a separate application and then save that into the wpf3d file
            // so that the application that requires models with EdgeLineIndices data will not need to calculate that.

            var edgeLinePositions = new Point3DCollection();

            EdgeLinesFactory.AddEdgeLinePositions(readModel3D, EdgeStartAngleSlider.Value, edgeLinePositions);

            _multiLineVisual3D = new MultiLineVisual3D()
            {
                Positions     = edgeLinePositions,
                LineColor     = Colors.Black,
                LineThickness = LineThicknessSlider.Value,
            };

            MainViewport.Children.Add(_multiLineVisual3D);


            if (_fileName != fileName) // Reset camera only when the file is loaded for the first time
            {
                _fileName = fileName;

                Camera1.TargetPosition = readModel3D.Bounds.GetCenterPosition();
                Camera1.Distance       = readModel3D.Bounds.GetDiagonalLength() * 1.2;
            }

            // Add ambient light
            var ambientLight = new AmbientLight(Color.FromRgb(100, 100, 100));

            MainViewport.Children.Add(ambientLight.CreateModelVisual3D());
        }
        /// <summary>
        /// Loads native assimp library from the specified folders.
        /// </summary>
        public static void LoadAssimpNativeLibrary(string assimp32BitLibraryFolder, string assimp64BitLibraryFolder)
        {
            // IMPORTANT:

            // To use assimp importer in your project, you need to prepare the native and the managed part of the library.
            //
            // 1) Prepare native assimp libraries
            //
            //    The core part of the assimp importer is its native library that is compiled into two dlls: Assimp32.dll and Assimp64.dll.
            //    One is for x86 application, the other for x64 applications.
            //
            //    The easiest way to to make those two libraries available to your application is to make sure that they are added to the compiler output folder.
            //    This can be done with adding the dlls to the root of your project and in the file properties set the "Copy to Output Directory" to "Copy is newer".
            //
            //    You can also provide the dlls in some other folder. In this case you need to call AssimpWpfImporter.LoadAssimpNativeLibrary method and
            //    in the parameters provide path to x86 and x64 version of the library.
            //
            //    If your project is not compiled for AnyCPU, then you can distribute only the version for your target platform.
            //
            //
            // 2) Ensure that Visual C++Redistributable for Visual Studio 2019 in available on the system
            //
            //    The native Assimp library requires that the Visual C++ Redistributable for Visual Studio 2019 is available on the system.
            //
            //    Visual C++ Redistributable for Visual Studio 2019 is installed on all Windows 10 systems and should be installed on
            //    all Windows Vista and newer systems that have automatic windows update enabled.
            //    More information about that can be read in the following article: https://docs.microsoft.com/en-us/cpp/windows/universal-crt-deployment?view=vs-2019/
            //
            //    If your application is deployed to a system prior to Windows 10 and without installed windows updates, then you have to provide the required dlls by yourself.
            //
            //    You have two options:
            //    a) The recommended way is to Install Visual Studio VCRedist (redistributable package files) to the target system.
            //       Installer can be downloaded from the https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads
            //       (click on vc_redist.x86.exe or vc_redist.x64.exe),
            //
            //    b) It is also possible to copy all required dlls with your application(see "Local deployment" part from the first link above).
            //
            //
            // 3) Add reference to managed libraries
            //
            //    After you have the native part set up, then you only need to add reference to:
            //    - Assimp.Net library (AssimpNet.dll file) that provides a managed wrapper for the native addimp library,
            //    - Ab3d.PowerToys.Assimp library (Ab3d.PowerToys.Assimp.dll file) that provides logic to convert assimp objects into WPF 3D objects.


            // In this sample both Assimp32.dll and Assimp64.dll are copied to output directory
            // and can be automatically found when needed.
            //
            // In such case it is not needed to call AssimpWpfImporter.LoadAssimpNativeLibrary as it is done below.
            // Here this is done for cases when there is a problem with loading assimp library because
            // Visual C++ Redistributable for Visual Studio 2015 is not installed on the system - in this case a message box is shown to the user.


            // To provide Assimp32 in its own folder, use the following path:
            //string assimp32Folder = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assimp32");
            //string assimp64Folder = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assimp64");

            // Here both Assimp32.dll and Assimp64.dll are available in output directory

            try
            {
                AssimpWpfImporter.LoadAssimpNativeLibrary(assimp32BitLibraryFolder, assimp64BitLibraryFolder); // This method can be called multiple times without problems
            }
            catch (AssimpException ex)
            {
                MessageBox.Show(
                    @"Error loading native assimp library!

The most common cause of this error is that the 
Visual C++ Redistributable for Visual Studio 2019
is not installed on the system. 

Please install it manually or contact support of the application.

Error message:
" + ex.Message);

                throw;
            }
        }
Пример #22
0
        public void LoadModel(string fileName)
        {
            bool isNewFile = false;

            // Create an instance of AssimpWpfImporter
            var assimpWpfImporter = new AssimpWpfImporter();

            string fileExtension = System.IO.Path.GetExtension(fileName);

            if (!assimpWpfImporter.IsImportFormatSupported(fileExtension))
            {
                MessageBox.Show("Assimp does not support importing files file extension: " + fileExtension);
                return;
            }

            try
            {
                Mouse.OverrideCursor = Cursors.Wait;

                // Before readin the file we can set the default material (used when no other materila is defined - here we set the default value again)
                assimpWpfImporter.DefaultMaterial = new DiffuseMaterial(Brushes.Silver);

                // After assimp importer reads the file, it can execute many post processing steps to transform the geometry.
                // See the possible enum values to see what post processes are available.
                // Here we just set the AssimpPostProcessSteps to its default value - execute the Triangulate step to convert all polygons to triangles that are needed for WPF 3D.
                // Note that if ReadPolygonIndices is set to true in the next line, then the assimpWpfImporter will not use assimp's triangulation because it needs original polygon data.
                assimpWpfImporter.AssimpPostProcessSteps = PostProcessSteps.Triangulate;

                // When ReadPolygonIndices is true, assimpWpfImporter will read PolygonIndices collection that can be used to show polygons instead of triangles.
                assimpWpfImporter.ReadPolygonIndices = ReadPolygonIndicesCheckBox.IsChecked ?? false;

                // Read model from file
                Model3D readModel3D;

                try
                {
                    readModel3D = assimpWpfImporter.ReadModel3D(fileName, texturesPath: null); // we can also define a textures path if the textures are located in some other directory (this is parameter can be skipped, but is defined here so you will know that you can use it)

                    isNewFile = (_fileName != fileName);
                    _fileName = fileName;
                }
                catch (Exception ex)
                {
                    readModel3D = null;
                    MessageBox.Show("Error importing file:\r\n" + ex.Message);
                }

                // After the model is read and if the object names are defined in the file,
                // you can get the model names by assimpWpfImporter.ObjectNames
                // or get object by name with assimpWpfImporter.NamedObjects

                // To get the  object model of the assimp importer, you can observe the assimpWpfImporter.ImportedAssimpScene

                // Show the model
                if (readModel3D != null)
                {
                    ShowModel(readModel3D, updateCamera: isNewFile); // If we just reloaded the previous file, we preserve the current camera TargetPosition and Distance
                }

                // Force garbage collection to clear the previously loaded objects from memory.
                // Note that sometimes when a lot of objects are created in large objects heap,
                // it may take two garbage collections to release the memory
                // (e.g. - after reading one large file, you will need to read two smaller files to clean the memory taken by the large file).
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
            }
            finally
            {
                // Dispose unmanaged resources
                assimpWpfImporter.Dispose();

                Mouse.OverrideCursor = null;
            }
        }
        private void LoadFile(string fileName, string customTexturesFolder)
        {
            if (!System.IO.File.Exists(fileName))
            {
                MessageBox.Show($"File does not exist:\r\n{fileName}");
                return;
            }

            Mouse.OverrideCursor = Cursors.Wait;

            ModelPlaceholder.Content = null;
            ModelPlaceholder.Children.Clear();
            //LightsModelPlaceholder.Children.Clear();

            InfoTextBlock.Text = "";
            Log("Start opening file: " + fileName);


            try
            {
                //// ReaderObj that comes with Ab3d.PowerToys cannot read normal map textures
                //var readerObj = new Ab3d.ReaderObj();
                //readerObj.BitmapCacheOption = BitmapCacheOption.None; // Do not load textures by ReaderObj
                //Model3D readModel3D = readerObj.ReadModel3D(fileName);

                //UpdateMaterials(readModel3D);


                // Therefore we need to use Assimp importer
                var assimpWpfImporter = new AssimpWpfImporter();

                // Let assimp calculate the tangents that are needed for normal mapping
                assimpWpfImporter.AssimpPostProcessSteps |= PostProcessSteps.CalculateTangentSpace;

                //var readModel3D = assimpWpfImporter.ReadModel3D(fileName, texturesPath: null); // we can also define a textures path if the textures are located in some other directory (this is parameter can be skipped, but is defined here so you will know that you can use it)

                var assimpScene = assimpWpfImporter.ReadFileToAssimpScene(fileName);

                if (assimpScene == null)
                {
                    Log("Cannot load 3D model probably because file format is not recognized!");
                    return;
                }

                var assimpWpfConverter = new AssimpWpfConverter();
                var readModel3D        = assimpWpfConverter.ConvertAssimpModel(assimpScene, System.IO.Path.GetDirectoryName(fileName));


                _loadedFileName = fileName;

                // Convert standard WPF materials into PhysicallyBasedMaterial objects
                UpdateMaterials(fileName, customTexturesFolder, assimpScene, assimpWpfConverter, useStrictFileNameMatch: true, supportDDSTextures: true);


                ModelPlaceholder.Content = readModel3D;

                Camera1.TargetPosition = readModel3D.Bounds.GetCenterPosition();
                Camera1.Distance       = 1.5 * readModel3D.Bounds.GetDiagonalLength();

                Log(string.Format("\r\nFile '{0}' loaded\r\nCenter position: {1:0};    Size: {2:0}",
                                  System.IO.Path.GetFileName(fileName),
                                  readModel3D.Bounds.GetCenterPosition(),
                                  readModel3D.Bounds.Size));


                Camera1.ShowCameraLight = ShowCameraLightType.Always;
                Camera1.Refresh();

                // Manually call Refresh to update the scene while we are still showing Wait cursor
                MainDXViewportView.Refresh();

                this.Title = "Ab3d.DXEngine PBR Rendering - " + System.IO.Path.GetFileName(fileName);
            }
            finally
            {
                Mouse.OverrideCursor = null;
            }
        }
Пример #24
0
        private void LoadModelWithEdgeLines(string fileName)
        {
            _mainViewport3D.Children.Clear();

            // Create an instance of AssimpWpfImporter
            var assimpWpfImporter = new AssimpWpfImporter();
            var readModel3D       = assimpWpfImporter.ReadModel3D(fileName, texturesPath: null); // we can also define a textures path if the textures are located in some other directory (this is parameter can be skipped, but is defined here so you will know that you can use it)


            if (readModel3D == null)
            {
                MessageBox.Show("Cannot read file");
                return;
            }

            _mainViewport3D.Children.Add(readModel3D.CreateModelVisual3D());


            // NOTE:
            // EdgeLinesFactory from Ab3d.PowerToys library will be used to create 3D lines that define the edges of the 3D models.
            // The edges are created when the angle between two triangles is bigger then the specified edgeStartAngleInDegrees (set by EdgeStartAngleSlider).
            //
            // See Lines3D\StaticEdgeLinesCreationSample and Lines3D\DynamicEdgeLinesSample samples for more info.
            //
            //
            // ADDITIONAL NOTE:
            // It is possible to store edge lines into wpf3d file format (this way it is not needed to generate the edge lines after the file is read).
            // The source code to read and write wpf3d file is available with Ab3d.PowerToys library.



            // With using AddEdgeLinePositions we will create STATIC lines from the current readModel3D.
            // If the readModel3D would be changed (any child transformation would be changed),
            // then the lines would not be correct any more.
            // See the DynamicEdgeLinesSample to see how to create dynamic edge lines.
            // If your object will not change, then it is better to create static edge lines for performance reasons
            // (having single MultiLineVisual3D for the whole instead of one MultiLineVisual3D for each GeometryModel3D).

            var edgeLinePositions = new Point3DCollection();

            EdgeLinesFactory.AddEdgeLinePositions(readModel3D, EdgeStartAngleSlider.Value, edgeLinePositions);


            _edgeLinesVisual3D = new MultiLineVisual3D()
            {
                Positions     = edgeLinePositions,
                LineColor     = Colors.Black,
                LineThickness = LineThicknessSlider.Value,
                IsVisible     = ShowEdgeLinesCheckBox.IsChecked ?? false
            };

            if (LineDepthBiasCheckBox.IsChecked ?? false)
            {
                // Use line depth bias to move the line closer to the camera so the line is rendered on top of solid model and is not partially occluded by it.
                double depthBias = (double)DepthBiasComboBox.SelectedItem;
                _edgeLinesVisual3D.SetDXAttribute(DXAttributeType.LineDepthBias, depthBias);
            }

            _mainViewport3D.Children.Add(_edgeLinesVisual3D);


            if (_fileName != fileName) // Reset camera only when the file is loaded for the first time
            {
                _fileName = fileName;

                // Set both Distance and CameraWidth (this way we can change the CameraType with RadioButtons)
                // Distance is used when CameraType is PerspectiveCamera.
                // CameraWidth is used when CameraType is OrthographicCamera.
                Camera1.Distance    = readModel3D.Bounds.GetDiagonalLength() * 1.3;
                Camera1.CameraWidth = readModel3D.Bounds.SizeX * 2;

                Camera1.TargetPosition = readModel3D.Bounds.GetCenterPosition() + new Vector3D(0, readModel3D.Bounds.SizeY * 0.15, 0); // slightly move the object down so that the object is not shown over the title

                // Find best EdgeLine DepthBias
                double idealDepthBias = Camera1.Distance / 3000;

                double bestDistance = double.MaxValue;
                int    bestIndex    = -1;
                for (int i = 0; i < PossibleEdgeLineDepthBiases.Length; i++)
                {
                    double distance = Math.Abs(idealDepthBias - PossibleEdgeLineDepthBiases[i]);
                    if (distance < bestDistance)
                    {
                        bestDistance = distance;
                        bestIndex    = i;
                    }
                }

                DepthBiasComboBox.SelectedIndex = bestIndex;
            }

            // Add ambient light
            var ambientLight = new AmbientLight(Color.FromRgb(100, 100, 100));

            _mainViewport3D.Children.Add(ambientLight.CreateModelVisual3D());
        }