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); }