Example #1
0
        private void AnimateSimpleMesh()
        {
            //if (_vertexBufferArray == null || _simpleMesh == null || !_simpleMesh.IsInitialized)
            if (_vertexBufferArray == null || _simpleMesh == null)
            {
                return;
            }

            if (!_simpleMesh.IsInitialized)
            {
                // We cannot call RecreateMesh on _simpleMesh, if it was not yet initialized (InitializeResources method called).
                //
                // _simpleMesh is usually initialized, when its parent SceneNode (_meshObjectNode) is added to the DXScene.
                // In our case this is done inside DXViewportView's Update method. This method is called
                // inside CompositionTraget.Rendering event handler in DXViewportView class.
                // But if AnimateSimpleMesh is called before the DXViewportView's Update method,
                // then we go inside this if statement.
                // Here we have a few options when _simpleMesh is not yet initialized:
                // - we could simply return and wait until the  _simpleMesh is automatically initialized (before the next call to AnimateSimpleMesh method)
                // - we can force updating DXViewportView with calling Update method:
                //   MainDXViewportView.Update();
                // - we can manually initialize _simpleMesh (and its parent _meshObjectNode).
                //   This is done with the following code:

                if (MainDXViewportView.DXScene != null)
                {
                    _meshObjectNode.InitializeResources(MainDXViewportView.DXScene);
                }
                else
                {
                    return; // UH: DXScene is not yet initialized (we cannot do much now)
                }
            }

            var elapsedSeconds = (DateTime.Now - _animationStartTime).TotalSeconds;

            // calculate animation factor (from 0 to 1) based on Sin function - repeats in 2*PI seconds
            // 0 - means original mesh
            // 1 - means morphed mesh
            float morphAnimationFactor = (float)(Math.Sin(elapsedSeconds - 0.5 * Math.PI) + 1.0f) * 0.5f;


            if (_stopwatch == null)
            {
                _stopwatch = new Stopwatch();
            }

            _stopwatch.Restart();

            var vertexCount = _vertexBufferArray.Length;

            float morphAnimationFactorNeg = 1.0f - morphAnimationFactor;

            for (int i = 0; i < vertexCount; i++)
            {
                // Morph position
                var p1 = _originalMesh[i].Position;
                var p2 = _morphedMesh[i].Position;

                _vertexBufferArray[i].Position = morphAnimationFactorNeg * p1 + morphAnimationFactor * p2;


                // Morph normal
                p1 = _originalMesh[i].Normal;
                p2 = _morphedMesh[i].Normal;

                var newNormal = morphAnimationFactorNeg * p1 + morphAnimationFactor * p2;
                newNormal.Normalize(); // After interpolation, the length of the normal can change so we need to re-normalize it again

                _vertexBufferArray[i].Normal = newNormal;
            }


            // To get the ultimate performance, it is possible to use unsafe code.
            // This is almost 50% faster then the managed code above (on i7 6700).
            // To run this code do the following;
            // - comment the for loop above,
            // - uncomment the code below,
            // - allow unsafe code in project properties (in build settings)
            //
            // NOTE:
            // With a little hack, similar approach can be used with MeshGeometry3D.
            // In order to use unsafe code efficiently when changing MeshGeometry3D Positions and Normals,
            // you need to first get access to the arrays that are used for Positions Point3DCollection
            // and for Normals Vector3DCollection.
            // To do that you will first need to read private _collection field to get FrugalStructList<T>,
            // then read _listStore on FrugalStructList<T> to get ArrayItemList<T>
            // and finally read _entries to get array of T.
            // Once you get the array of T, you can use pointers to write data much faster then when using
            // Point3DCollection or Vector3DCollection setters.

            //GCHandle originalMeshHandle = GCHandle.Alloc(_originalMesh, GCHandleType.Pinned);
            //GCHandle morphedMeshHandle = GCHandle.Alloc(_morphedMesh, GCHandleType.Pinned);
            //GCHandle vertexBufferArrayHandle = GCHandle.Alloc(_vertexBufferArray, GCHandleType.Pinned);

            //try
            //{
            //    unsafe
            //    {
            //        float* originalMeshPtr = ((float*)originalMeshHandle.AddrOfPinnedObject().ToPointer());
            //        float* morphedMeshPtr = ((float*)morphedMeshHandle.AddrOfPinnedObject().ToPointer());
            //        float* vertexBufferArrayPtr = ((float*)vertexBufferArrayHandle.AddrOfPinnedObject().ToPointer());

            //        for (int i = 0; i < vertexCount; i++)
            //        {
            //            // Morph position

            //            // var p1 = _originalMesh[i].Position;
            //            float p1x = *originalMeshPtr;
            //            float p1y = *(originalMeshPtr + 1);
            //            float p1z = *(originalMeshPtr + 2);

            //            // var p2 = _morphedMesh[i].Position;
            //            float p2x = *morphedMeshPtr;
            //            float p2y = *(morphedMeshPtr + 1);
            //            float p2z = *(morphedMeshPtr + 2);

            //            // _vertexBufferArray[i].Position = morphAnimationFactorNeg * p1 + morphAnimationFactor * p2;
            //            float px = morphAnimationFactorNeg * p1x + morphAnimationFactor * p2x;
            //            float py = morphAnimationFactorNeg * p1y + morphAnimationFactor * p2y;
            //            float pz = morphAnimationFactorNeg * p1z + morphAnimationFactor * p2z;

            //            *vertexBufferArrayPtr       = px;
            //            *(vertexBufferArrayPtr + 1) = py;
            //            *(vertexBufferArrayPtr + 2) = pz;


            //            // Morph normal
            //            // p1 = _originalMesh[i].Normal;
            //            p1x = *(originalMeshPtr + 3);
            //            p1y = *(originalMeshPtr + 4);
            //            p1z = *(originalMeshPtr + 5);

            //            // p2 = _morphedMesh[i].Normal;
            //            p2x = *(morphedMeshPtr + 3);
            //            p2y = *(morphedMeshPtr + 4);
            //            p2z = *(morphedMeshPtr + 5);

            //            // var newNormal = morphAnimationFactorNeg * p1 + morphAnimationFactor * p2;
            //            px = morphAnimationFactorNeg * p1x + morphAnimationFactor * p2x;
            //            py = morphAnimationFactorNeg * p1y + morphAnimationFactor * p2y;
            //            pz = morphAnimationFactorNeg * p1z + morphAnimationFactor * p2z;

            //            // newNormal.Normalize(); // After interpolation, the length of the normal can change so we need to re-normalize it again

            //            // Multiplying is faster then dividing
            //            // So we divide once and the make three multiplications
            //            // This is actually significantly faster then doing 3 divisions (on i7)
            //            float length = 1.0f / (float)Math.Sqrt(px * px + py * py + pz * pz);

            //            // _vertexBufferArray[i].Normal = newNormal;
            //            *(vertexBufferArrayPtr + 3) = px * length;
            //            *(vertexBufferArrayPtr + 4) = py * length;
            //            *(vertexBufferArrayPtr + 5) = pz * length;


            //            originalMeshPtr += 6;
            //            morphedMeshPtr += 6;
            //            vertexBufferArrayPtr += 8; // IMPORTANT: This works only for array of PositionNormalTexture structs (3 + 3 + 2) * float
            //        }
            //    }
            //}
            //finally
            //{
            //    originalMeshHandle.Free();
            //    morphedMeshHandle.Free();
            //}

            _meshMorphTimes.Add(_stopwatch.Elapsed.TotalMilliseconds);
            _stopwatch.Reset();

            // Calculate the current bounding box (bounds) from interpolating between _originalBoundingBox and _morphedBoundingBox
            Vector3 boundsMin = morphAnimationFactor * _originalBoundingBox.Minimum + (1.0f - morphAnimationFactor) * _morphedBoundingBox.Minimum;
            Vector3 boundsMax = morphAnimationFactor * _originalBoundingBox.Maximum + (1.0f - morphAnimationFactor) * _morphedBoundingBox.Maximum;

            _simpleMesh.Bounds = new Bounds(new BoundingBox(boundsMin, boundsMax));

            // Recreate the DirectX vertex buffer
            _simpleMesh.RecreateMesh(recreateVertexBuffer: true, recreateIndexBuffer: false, updateBounds: false);

            // After mesh has been recreated, we also need to call UpdateMesh on MeshObjectNode
            _meshObjectNode.UpdateMesh();

            _stopwatch.Stop();

            _meshUpdateTimes.Add(_stopwatch.Elapsed.TotalMilliseconds);

            // When we are working with SceneNode objects, we need manually notify all the SceneNodes that need to be updated.
            _meshObjectNode.NotifySceneNodeChange(SceneNode.SceneNodeDirtyFlags.MeshVertexBufferDataChanged | SceneNode.SceneNodeDirtyFlags.BoundsChanged);
            _meshObjectNode.NotifyAllParentSceneNodesChange(SceneNode.SceneNodeDirtyFlags.ChildBoundsChanged);


            UpdateStatistics((int)elapsedSeconds);
        }