helix-toolkit / helix-toolkit

Helix Toolkit is a collection of 3D components for .NET.
http://helix-toolkit.org/
MIT License
1.88k stars 671 forks source link

Load and unload Model3DGroup dynamically? #547

Closed antithing closed 7 years ago

antithing commented 7 years ago

I have a changing list, from which I am reading data. (position values, and an ID for each entry). What i am trying to do, is load and unload Models into a helix Viewport as the list updates. I have approached this by loading 50 (the max possible number of list entries) into a vector, and then adding and removing them from the Model3DGroup that is set as the content for the ModelVisual3D. This works, but causes a lot of flickering. Is there a more sensible way to approach this?

Right now I have:

        //marker model
        public Model3DGroup modelMarker;

        //parent group
        public Model3DGroup rootGrp;

       //list of all objects
        public List<Model3DGroup> markerModels = new List<Model3DGroup>();

         //importer
           ModelImporter import = new ModelImporter();
            modelMarker = import.Load("data\\assets\\Marker.obj"); // load object

            rootGrp = new Model3DGroup();

            //load 50 markers into list
                for (int i=0;i< 50;i++)
               {
                    var markerTmp = new Model3DGroup();
                    markerTmp = modelMarker;
                    markerModels.Add(markerTmp);
                }

            model.Content = rootGrp; //model is the ModelVisual3D, set parent node as content

And (On a dispatcher timer):

      tmpList = Dense.pullData(); // fills list
                    if (tmpList.Count != 0) // if list is not empty
                    {
                        rootGrp.Children.Clear(); // clear the parent group

                        for (int i = 0; i < tmpList.Count; i++) // for each list entry
                        {
                            int index = Convert.ToInt32(tmpList[i].id); // the ID of each object
                            transMarker.X = tmpList[i].position[0];  // the positional data
                            transMarker.Y = tmpList[i].position[1] ;
                            transMarker.Z = tmpList[i].position[2];

                            quatMarker.X = tmpList[i].rotationQuats[0]; // the rotation data
                            quatMarker.Y = tmpList[i].rotationQuats[1];
                            quatMarker.Z = tmpList[i].rotationQuats[2] ;
                            quatMarker.W = tmpList[i].rotationQuats[3];

                            var matrixMarker = new Matrix3D(); // create a new Matrix3d
                            matrixMarker.Rotate(quatMarker);
                            matrixMarker.Translate(transMarker);

                          rootGrp.Children.Add(markerModels[index]);  // add to the parent group

                             //transform the object
                            markerModels[index].Transform = new MatrixTransform3D(matrixMarker);
                        }

This works, but the objects flicker on and off, and are never all displayed at the same time. Is there a smarter way to do this?

thank you.

holance commented 7 years ago

To improve performance, instead of clear group, try to create a new group, add all elements, then replace the old group inside viewport. Otherwise, each time collection changed, the visual will refresh, causes flicking.

antithing commented 7 years ago

Thank you! i now have this, but see the same flickering...

                  tmpList = Dense.pullFiducialData();

                    if (tmpList.Count != 0)
                    {
                        Model3DGroup root = new Model3DGroup();

                        for (int i = 0; i < tmpList.Count; i++)
                        {

                            int index = Convert.ToInt32(tmpList[i].id);

                            transMarker.X = tmpList[i].position[0];
                            transMarker.Y = tmpList[i].position[1];
                            transMarker.Z = tmpList[i].position[2];

                            quatMarker.X = tmpList[i].rotationQuats[0];
                            quatMarker.Y = tmpList[i].rotationQuats[1];
                            quatMarker.Z = tmpList[i].rotationQuats[2];
                            quatMarker.W = tmpList[i].rotationQuats[3];

                            var matrixMarker = new Matrix3D();
                            matrixMarker.Rotate(quatMarker);
                            matrixMarker.Translate(transMarker);

                            root.Children.Add(markerModels[index]);  // add to the parent group

                            //transform the object
                            markerModels[index].Transform = new MatrixTransform3D(matrixMarker);
                        }
                        model.Content = root;
                    }

                }

Is this what you meant?

Thanks again.

holance commented 7 years ago

Are you trying to move the existing object around? If it is, you can simply update the each model's Transform instead of load/unload them in each frame. Update model is expensive.

antithing commented 7 years ago

Yes, I move each visible object around. The visibility is determined by a constantly updating List, so the objects that are visible is changing all the time.

antithing commented 7 years ago

.. would I be better to just add everything at once, leave the Models loaded, and transform the 'invisible' ones 10000 units away?

holance commented 7 years ago

Do all the models use the same mesh? WPF internal 3D engine is slow on handling complex scene. If you are trying to render large number of similar meshes, you better switch to use SharpDX version and use instancing to let GPU do its work.

antithing commented 7 years ago

they do, yes. I have updated my code, but see the same issue. Any more ideas would be greatly appreciated!

The mesh objects are very light (basically cubes), and there are only two of them in my testing, yet i cannot get rid of this flickering.

code is:

 public FormViewport()
        {

            InitializeComponent();
            ModelImporter import = new ModelImporter();

            Material material = new DiffuseMaterial(new SolidColorBrush(Colors.DarkGray));
            import.DefaultMaterial = material;

            modelMarker = import.Load("data\\assets\\Marker.obj");

            //load markers
                for (int i=0;i< 50;i++)
               {
                    var markerTmp = new Model3DGroup();
                    markerTmp = modelMarker;
                    markerModels.Add(markerTmp);
                    rootGrp.Children.Add(markerModels[i]);
                }

                 rootGrp = new Model3DGroup();         
                model.Content = rootGrp;

            //timer 
            DispatcherTimer timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromMilliseconds(10);
            timer.Tick += timer_Tick;
            timer.Start();

        }

..and in the timer tick:

 void timer_Tick(object sender, EventArgs e)
        {            
            var transMarker = new Vector3D(0, 0, 0);
            var quatMarker = new Quaternion(0, 0, 0, 1);

            if (Dense.status == 1)
            {              
                if (Dense.Fiducial == true && Dense.MarkersFound == 1)
                {

                    tmpList = Dense.pullFiducialData();

                    if (tmpList.Count != 0)
                    {

                        for (int i = 0; i < tmpList.Count; i++)
                        {

                            int index = Convert.ToInt32(tmpList[i].id);

                            transMarker.X = tmpList[i].position[0] / 10;
                            transMarker.Y = tmpList[i].position[1] * -1 / 10;
                            transMarker.Z = tmpList[i].position[2] / 10;

                            quatMarker.X = tmpList[i].rotationQuats[0] * -1;
                            quatMarker.Y = tmpList[i].rotationQuats[1];
                            quatMarker.Z = tmpList[i].rotationQuats[2] * -1;
                            quatMarker.W = tmpList[i].rotationQuats[3];

                            var matrixMarker = new Matrix3D();
                            matrixMarker.Rotate(quatMarker);
                            matrixMarker.Translate(transMarker);

                            //transform the object
                            if (index < markerModels.Count)
                            {                              
                                markerModels[index].Transform = new MatrixTransform3D(matrixMarker);
                            }

                        }

                    }

                }

            }
        }

The result I expect would be that 50 objects load, then two of them are moved.

What actually happens is that 50 objects are loaded, then when I start the data stream, the two that I expect to move do in fact just to the right position, but I can only see one at a time, and it jumps from one to the other. The other 48 vanish.

the data comes in as (id, tx, ty, tz, qx, qy, qz, qw), I have checked it, and it is valid. the test stream is sending ID 10, then 12, then 10, then 12 and so on.

How can I solve this?

thank you again.

holance commented 7 years ago

I would suggest to change to use sharpdx version. There is an instancing demo, which is doing exactly what you want.

antithing commented 7 years ago

Ok I will try that. Is this it?

https://www.nuget.org/packages/HelixToolkit.Wpf.SharpDX/

holance commented 7 years ago

You can download the source code, or use myget link from the home page of this project. The lastest build is the unstable version

antithing commented 7 years ago

Sorry, i cant see that link, could you point me at it? Or is the NuGet version above ok?

Also, where can I find samples?

thanks again for taking the time to help me.

antithing commented 7 years ago

Ah, it is in the master branch. Got it! thank you again.

antithing commented 7 years ago

One last question... Is there a ModelImporter in the SharpDx version? I need to import objs..

antithing commented 7 years ago

public void LoadObj(string path) { var reader = new ObjReader(); var objCol = reader.Read(path); AttachModelList(objCol); }

Consider this solved. thank you again.