File: System\Windows\Media3D\Viewport2DVisual3D.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using MS.Internal;
using MS.Internal.Media3D;
using MS.Internal.KnownBoxes;
using System.Windows.Markup;
 
namespace System.Windows.Media.Media3D
{
    /// <summary>
    /// The Viewport2DVisual3D class represents the link from 3D back to 2D, just as
    /// Viewport3DVisual represents the link from 2D in to 3D.
    /// </summary>
    [ContentProperty("Visual")]
    public sealed class Viewport2DVisual3D : Visual3D
    {
        /// <summary>
        /// Constructs a new Viewport2DVisual3D
        /// </summary>
        public Viewport2DVisual3D()
        {
            _visualBrush = CreateVisualBrush();
            _bitmapCacheBrush = CreateBitmapCacheBrush();
 
 
            // create holders for the content
            // We don't want this model to set itself as the IC for Geometry and Material 
            // so we set to to not be able to be an inheritance context.
            GeometryModel3D model = new GeometryModel3D();
            model.CanBeInheritanceContext = false;
            
            Visual3DModel = model; 
        } 
        
        internal static bool Get3DPointFor2DCoordinate(Point point, 
                                                       out Point3D point3D,
                                                       Point3DCollection positions,
                                                       PointCollection textureCoords,
                                                       Int32Collection triIndices)
        {
            point3D = new Point3D();
            
            // walk through the triangles - and look for the triangles we care about
            Point3D[] p = new Point3D[3];
            Point[] uv = new Point[3];
 
            if (positions != null && textureCoords != null)
            {
                if (triIndices == null || triIndices.Count == 0)
                {
                    int texCoordCount = textureCoords.Count;
                
                    // in this case we have a non-indexed mesh
                    int count = positions.Count;
                    count = count - (count % 3);
 
                    for (int i = 0; i < count; i+=3)
                    {
                        for (int j = 0; j < 3; j++)
                        {
                            p[j] = positions[i + j];
 
                            if (i + j < texCoordCount)
                            {
                                uv[j] = textureCoords[i + j];
                            }
                            else
                            {
                                // In the case you have less texture coordinates than positions, MIL will set
                                // missing ones to be 0,0.  We do the same to stay consistent. 
                                // See CMILMesh3D::CopyTextureCoordinatesFromDoubles
                                uv[j] = new Point(0, 0);
                            }
                        }
 
                        if (M3DUtil.IsPointInTriangle(point, uv, p, out point3D))
                        {
                            return true;
                        }
                    }
                }
                else
                {
                    // in this case we have an indexed mesh
                    int posLimit = positions.Count;
                    int texCoordLimit = textureCoords.Count;
                    
                    for (int i = 2, count=triIndices.Count; i < count; i += 3)
                    {
                        bool validTextureCoordinates = true;
                        for (int j = 0; j < 3; j++)
                        {
                            // subtract 2 to take in to account we start i
                            // at the high range of indices                        
                            int index = triIndices[(i-2) + j];
 
                            // if a point or texture coordinate is out of range, end early since this is an error
                            if (index < 0 || index >= posLimit)
                            {
                                // no need to look anymore - see MeshGeometry3D RayHitTestIndexedList for 
                                // reasoning why we stop
                                return false; 
 
                            }
                            if (index < 0 || index >= texCoordLimit)
                            {
                                validTextureCoordinates = false;
                                break;
                            }
                            
                            p[j] = positions[index];
                            uv[j] = textureCoords[index];
                        }
 
                        if (validTextureCoordinates)
                        {
                            if (M3DUtil.IsPointInTriangle(point, uv, p, out point3D))
                            {
                                return true;
                            }
                        }
                    }                    
                }
            }
 
            return false;
        }
        
        /// <summary>
        /// Converts a point given in texture coordinates to the corresponding
        /// 2D point on the UIElement passed in.
        /// </summary>
        /// <param name="uv">The texture coordinate to convert</param>
        /// <param name="visual">The UIElement whose coordinate system is to be used</param>
        /// <returns>
        /// The 2D point on the passed in UIElement cooresponding to the
        /// passed in texture coordinate. 
        /// </returns>
        internal static Point TextureCoordsToVisualCoords(Point uv, Visual visual)
        {
            return TextureCoordsToVisualCoords(uv, visual.CalculateSubgraphRenderBoundsOuterSpace());           
        }
 
        // same as the above except we now take the rectangle giving the bounds of the visual
        // rather than the visual itself
        internal static Point TextureCoordsToVisualCoords(Point uv, Rect descBounds)
        {
            return new Point(uv.X * descBounds.Width + descBounds.Left,
                             uv.Y * descBounds.Height + descBounds.Top);                             
        }
 
        /// <summary>
        /// Returns true and the intersection point for the given rayHitResult if there is an intersection,
        /// and false otherwise.
        /// </summary>
        /// <param name="rayHitResult"></param>
        /// <param name="outputPoint">The output point if there was an intersection</param>
        /// <returns>
        /// Returns the point of intersection in outputPoint if there is one, and returns true
        /// to indicate this.
        /// </returns>
        internal static bool GetIntersectionInfo(RayHitTestResult rayHitResult, out Point outputPoint)
        {
            bool success = false;
            outputPoint = new Point();
 
            // try to cast to a RaymeshGeometry3DHitTestResult
            RayMeshGeometry3DHitTestResult rayMeshResult = rayHitResult as RayMeshGeometry3DHitTestResult;
            if (rayMeshResult != null)
            {
                // we can now extract the mesh and visual for the object we hit
                MeshGeometry3D geom = rayMeshResult.MeshHit;
 
                // pull the barycentric coordinates of the intersection point
                double vertexWeight1 = rayMeshResult.VertexWeight1;
                double vertexWeight2 = rayMeshResult.VertexWeight2;
                double vertexWeight3 = rayMeshResult.VertexWeight3;
 
                // the indices in to where the actual intersection occurred
                int index1 = rayMeshResult.VertexIndex1;
                int index2 = rayMeshResult.VertexIndex2;
                int index3 = rayMeshResult.VertexIndex3;
 
                PointCollection textureCoordinates = geom.TextureCoordinates;
 
                // texture coordinates of the three vertices hit
                // in the case that no texture coordinates are supplied we will simply
                // treat it as if no intersection occurred
                if (textureCoordinates != null &&
                    index1 < textureCoordinates.Count &&
                    index2 < textureCoordinates.Count &&
                    index3 < textureCoordinates.Count)
                {
                    Point texCoord1 = textureCoordinates[index1];
                    Point texCoord2 = textureCoordinates[index2];
                    Point texCoord3 = textureCoordinates[index3];
 
                    // get the final uv values based on the barycentric coordinates
                    outputPoint = new Point(texCoord1.X * vertexWeight1 +
                                            texCoord2.X * vertexWeight2 +
                                            texCoord3.X * vertexWeight3,
                                            texCoord1.Y * vertexWeight1 +
                                            texCoord2.Y * vertexWeight2 +
                                            texCoord3.Y * vertexWeight3);
                    success = true;
                }
            }
 
            return success;
        }
 
        /// <summary>
        /// Converts a point on the passed in UIElement to the corresponding
        /// texture coordinate for that point.  The function assumes (0, 0)
        /// is the upper-left texture coordinate and (1,1) is the lower-right.
        /// </summary>
        /// <param name="pt">The 2D point on the passed in UIElement to convert</param>
        /// <param name="visual">The UIElement whose coordinate system is being used</param>
        /// <returns>
        /// The texture coordinate corresponding to the 2D point on the passed in UIElement
        /// </returns>
        internal static Point VisualCoordsToTextureCoords(Point pt, Visual visual)
        {
            return VisualCoordsToTextureCoords(pt, visual.CalculateSubgraphRenderBoundsOuterSpace()); 
        }        
 
        // same as the above except we now take the rectangle giving the bounds of the visual
        // rather than the visual itself
        internal static Point VisualCoordsToTextureCoords(Point pt, Rect descBounds)
        {            
            return new Point((pt.X - descBounds.Left) / (descBounds.Right - descBounds.Left),
                             (pt.Y - descBounds.Top) / (descBounds.Bottom - descBounds.Top));
        }
 
        /// <summary>
        /// GenerateMaterial creates the material for the InteractiveModelVisual3D.  The
        /// material is composed of the Visual, which is displayed on a VisualBrush on a 
        /// DiffuseMaterial, as well as any post materials which are also applied.
        /// </summary>
        private void GenerateMaterial()
        {
            Material material = Material;
 
            // We clone the material so that we can modify parts of it without affecting the
            // original material that it came from.
            if (material != null)
            {
                material = material.CloneCurrentValue();
            }
 
            ((GeometryModel3D)Visual3DModel).Material = material;
            
            if (material != null)
            {
                SwapInCyclicBrush(material);
            }       
        }
        
        /// <summary>
        /// The visual applied to the VisualBrush, which is then used on the 3D object.
        /// 
        /// We AddOwner this property to get the same special treatment as VisualBrush's VisualProperty
        /// gets in InheritanceContext linkups and because both properties are used to describe the 
        /// Visual content of the owner.
        ///
        /// </summary>
        public static readonly DependencyProperty VisualProperty =
            VisualBrush.VisualProperty.AddOwner(
                            typeof(Viewport2DVisual3D),
                            new PropertyMetadata(null, new PropertyChangedCallback(OnVisualChanged)));
 
        /// <summary>
        /// </summary>        
        public Visual Visual
        {
            get { return (Visual)GetValue(VisualProperty); }
            set { SetValue(VisualProperty, value); }
        }
 
        /// <summary>
        /// The visual brush that the internal visual is contained on.
        /// </summary>
        private VisualBrush InternalVisualBrush
        {
            get { return _visualBrush; }
            set { _visualBrush = value; }
        }
 
        private BitmapCacheBrush InternalBitmapCacheBrush
        {
            get { return _bitmapCacheBrush; }
            set { _bitmapCacheBrush = value; }
        }
        
        internal static void OnVisualChanged(Object sender, DependencyPropertyChangedEventArgs e)
        {
            Viewport2DVisual3D viewport2DVisual3D = ((Viewport2DVisual3D)sender);
 
            // remove the old parent, add on a new one
            Visual oldValue = (Visual)e.OldValue;
            Visual newValue = (Visual)e.NewValue;
 
            if (oldValue != newValue)
            {   
                //
                // The following code deals with properly setting up the new child to have its inheritance context
                // only point towards this Viewport2DVisual3D.
                //
                // When we add the new visual as a child, if that visual is an FE (which most will be) we expect it to
                // clear the inheritance context (IC) since it has a visual parent.  In the case of a non-FE they don't
                // deal with ICs anyway, so they should have a null IC.  The Assert that follows then guards against 
                // the child not having a null inheritance context.
                //
                // We then set the target Visual on the internal brush to be this new visual.  Since when we created
                // the brush we set it to not be an inheritance context, the InheritanceContext should still be null.
                //
                // We become the IC after returning from this function, since the function that calls this change handler
                // will set us as the IC.
                //
 
                if (viewport2DVisual3D.CacheMode as BitmapCache != null)
                {
                    viewport2DVisual3D.InternalBitmapCacheBrush.Target = newValue;
                    Debug.Assert((newValue == null || newValue.InheritanceContext == null), 
                                 "Expected BitmapCacheBrush to remove the InheritanceContext on newValue");
                }
                else
                {
                    // Add ourselves as the parent of the object
                    viewport2DVisual3D.RemoveVisualChild(oldValue);
                    viewport2DVisual3D.AddVisualChild(newValue);
 
                    Debug.Assert((newValue == null || newValue.InheritanceContext == null), 
                                 "Expected AddVisualChild to remove the InheritanceContext on newValue");
 
                    // Change the brush's target
                    viewport2DVisual3D.InternalVisualBrush.Visual = newValue;
 
                    // setting the visual brush to use this new child should not invalidate our previous condition
                    Debug.Assert((newValue == null || newValue.InheritanceContext == null), 
                                 "Expected the InternalVisualBrush not to set the InheritanceContext");
                }
            }
        }
 
        /// <summary>
        /// AttachChild
        ///
        ///    This method is called to add a 2D Visual child to the Viewport2DVisual3D
        ///
        /// </summary>
        private void AddVisualChild(Visual child)
        {
            if (child == null)
            {
                return;
            }
 
            if (child._parent != null)
            {
                throw new ArgumentException(SR.Visual_HasParent);
            }
 
            // Set the parent pointer.
 
            child._parent = this;
 
            // NOTE: Since the 2D object is on a VisualBrush, it will allow it to handle
            // the dirtyness of the 2D object, realization information, as well as layout.  See
            // Visual(3D).AddVisualChild for the things they propagate on adding a new child
            
            // Fire notifications
            this.OnVisualChildrenChanged(child, null /* no removed child */);
            child.FireOnVisualParentChanged(null);
        }
 
        /// <summary>
        /// DisconnectChild
        ///
        ///    This method is called to remove the 2D visual child of the Viewport2DVisual3D
        ///
        /// </summary>
        private void RemoveVisualChild(Visual child)
        {
            if (child == null || child._parent == null)
            {
                return;
            }
 
            if (child._parent != this)
            {
                throw new ArgumentException(SR.Visual_NotChild);
            }
 
            // NOTE: We'll let the VisualBrush handle final cleanup from the channel
            //
           
            child._parent = null;
 
            // NOTE: We also let the VisualBrush handle any flag propagation issues (so Visual(3D).RemoveVisualChild for
            //       the things they propagate) as well as layout.
            
            // Fire notifications
            child.FireOnVisualParentChanged(this);
            OnVisualChildrenChanged(null /* no child added */, child);
        }
 
        /// <summary>
        /// Creates the VisualBrush that will be used to hold the interactive
        /// 2D content.
        /// </summary>
        /// <returns>The VisualBrush to hold the interactive 2D content</returns>
        private VisualBrush CreateVisualBrush()
        {
            VisualBrush vb = new VisualBrush();
 
            // We don't want the VisualBrush being the InheritanceContext for the Visual it contains.  Rather we want
            // that to be the Viewport2DVisual3D itself.
            vb.CanBeInheritanceContext = false;
            
            vb.ViewportUnits = BrushMappingMode.Absolute;
            vb.TileMode = TileMode.None;            
 
            // set any rendering options in the visual brush - we do this to still give access to these caching hints
            // without exposing the visual brush
            RenderOptions.SetCachingHint(vb, (CachingHint)GetValue(CachingHintProperty));
            RenderOptions.SetCacheInvalidationThresholdMinimum(vb, (double)GetValue(CacheInvalidationThresholdMinimumProperty));
            RenderOptions.SetCacheInvalidationThresholdMaximum(vb, (double)GetValue(CacheInvalidationThresholdMaximumProperty));
            
            return vb;
        }
        
        /// <summary>
        /// Creates the BitmapCacheBrush that will be used to hold the interactive
        /// 2D content.
        /// </summary>
        /// <returns>The BitmapCacheBrush to hold the interactive 2D content</returns>
        private BitmapCacheBrush CreateBitmapCacheBrush()
        {
            BitmapCacheBrush bcb = new BitmapCacheBrush();
 
            // We don't want the cache brush being the InheritanceContext for the Visual it contains.  Rather we want
            // that to be the Viewport2DVisual3D itself.
            bcb.CanBeInheritanceContext = false;
 
            // Ensure that the brush supports rendering all properties on the Visual to match VisualBrush behavior.
            bcb.AutoWrapTarget = true;
            
            bcb.BitmapCache = CacheMode as BitmapCache;
            return bcb;
        }
        
        /// <summary>
        /// Replaces any instances of the sentinal brush with the internal brush
        /// </summary>
        /// <param name="material">The material to look through</param>
        private void SwapInCyclicBrush(Material material)
        {
            int numMaterialsSwapped = 0;
            Stack<Material> materialStack = new Stack<Material>();
            materialStack.Push(material);
 
            Brush internalBrush = (CacheMode as BitmapCache != null) ? (Brush)InternalBitmapCacheBrush : (Brush)InternalVisualBrush;
            
            while (materialStack.Count > 0)
            {
                Material currMaterial = materialStack.Pop();
 
                if (currMaterial is DiffuseMaterial)
                {
                    DiffuseMaterial diffMaterial = (DiffuseMaterial)currMaterial;
                    if ((Boolean)diffMaterial.GetValue(Viewport2DVisual3D.IsVisualHostMaterialProperty))
                    {
                        diffMaterial.Brush = internalBrush;
                        numMaterialsSwapped++;
                    }
                }
                else if (currMaterial is EmissiveMaterial)
                {
                    EmissiveMaterial emmMaterial = (EmissiveMaterial)currMaterial;
                    if ((Boolean)emmMaterial.GetValue(Viewport2DVisual3D.IsVisualHostMaterialProperty))
                    {
                        emmMaterial.Brush = internalBrush;
                        numMaterialsSwapped++;
                    }
                }
                else if (currMaterial is SpecularMaterial)
                {
                    SpecularMaterial specMaterial = (SpecularMaterial)currMaterial;
                    if ((Boolean)specMaterial.GetValue(Viewport2DVisual3D.IsVisualHostMaterialProperty))
                    {
                        specMaterial.Brush = internalBrush;
                        numMaterialsSwapped++;
                    }
                }
                else if (currMaterial is MaterialGroup)
                {
                    MaterialGroup matGroup = (MaterialGroup)currMaterial;
 
                    // the IsVisualHostMaterialProperty should not be set on a MaterialGroup - verify that
                    if ((Boolean)matGroup.GetValue(Viewport2DVisual3D.IsVisualHostMaterialProperty))
                    {
                        throw new ArgumentException(SR.Viewport2DVisual3D_MaterialGroupIsInteractiveMaterial, "material");
                    }
 
                    // iterate over the children and put them on the stack of materials to modify
                    MaterialCollection children = matGroup.Children;
                    
                    if (children != null)
                    {
                        for (int i=0, count = children.Count; i < count; i++)
                        {
                            Material m = children[i];
                            materialStack.Push(m);
                        }
                    }
                }
                else
                {
                    Invariant.Assert(true, "Unexpected Material type encountered.  V2DV3D handles DiffuseMaterial, EmissiveMaterial, SpecularMaterial, and MaterialGroup.");
                }
            }
 
            // throw if there is more than 1 interactive material
            if (numMaterialsSwapped > 1)
            {
                throw new ArgumentException(SR.Viewport2DVisual3D_MultipleInteractiveMaterials, "material");
            }
        }
       
        /// <summary>
        /// The 3D geometry that the InteractiveModelVisual3D represents
        /// </summary>
        public static readonly DependencyProperty GeometryProperty =
            DependencyProperty.Register(
                "Geometry",
                typeof(Geometry3D),
                typeof(Viewport2DVisual3D),
                new PropertyMetadata(null, new PropertyChangedCallback(OnGeometryChanged)));
 
        /// <summary>
        /// </summary>        
        public Geometry3D Geometry
        {
            get { return (Geometry3D)GetValue(GeometryProperty); }
            set { SetValue(GeometryProperty, value); }
        }
 
        internal static void OnGeometryChanged(Object sender, DependencyPropertyChangedEventArgs e)
        {
            Viewport2DVisual3D viewport2DVisual3D = ((Viewport2DVisual3D)sender);
 
            viewport2DVisual3D.InvalidateAllCachedValues();
 
            if (!e.IsASubPropertyChange)
            {
                ((GeometryModel3D)viewport2DVisual3D.Visual3DModel).Geometry = viewport2DVisual3D.Geometry;
            }
        }
 
        private void InvalidateAllCachedValues()
        {
            // invalidate all of them
            InternalPositionsCache = null;
            InternalTextureCoordinatesCache = null;
            InternalTriangleIndicesCache = null;
        }
 
        // the cache of frozen positions for use with the various transforms
        internal Point3DCollection InternalPositionsCache
        {
            get
            {
                if (_positionsCache == null)
                {
                    Debug.Assert(Geometry == null || Geometry is MeshGeometry3D);
                    
                    MeshGeometry3D geometry = Geometry as MeshGeometry3D;
                    if (geometry != null)
                    {
                        _positionsCache = geometry.Positions;
                        if (_positionsCache != null)
                        {
                            _positionsCache = (Point3DCollection)_positionsCache.GetCurrentValueAsFrozen();
                        }
                    }
                }
 
                return _positionsCache;
            }
            set
            {
                _positionsCache = value;
            }
        }
 
        // the cache of frozen internal texture coordinates
        internal PointCollection InternalTextureCoordinatesCache
        {
            get
            {
                if (_textureCoordinatesCache == null)
                {
                    Debug.Assert(Geometry == null || Geometry is MeshGeometry3D);
                    
                    MeshGeometry3D geometry = Geometry as MeshGeometry3D;
                    if (geometry != null)
                    {
                        _textureCoordinatesCache= geometry.TextureCoordinates;
                        if (_textureCoordinatesCache != null)
                        {
                            _textureCoordinatesCache = (PointCollection)_textureCoordinatesCache.GetCurrentValueAsFrozen();
                        }
                    }
                }
 
                return _textureCoordinatesCache;
            }
            set
            {
                _textureCoordinatesCache = value;
            }
        }
 
        // the cache of frozen internal triangle indices
        internal Int32Collection InternalTriangleIndicesCache
        {
            get
            {
                if (_triangleIndicesCache== null)
                {
                    Debug.Assert(Geometry == null || Geometry is MeshGeometry3D);
                    
                    MeshGeometry3D geometry = Geometry as MeshGeometry3D;
                    if (geometry != null)
                    {
                        _triangleIndicesCache = geometry.TriangleIndices;
                        if (_triangleIndicesCache != null)
                        {
                            _triangleIndicesCache = (Int32Collection)_triangleIndicesCache.GetCurrentValueAsFrozen();
                        }
                    }
                }
 
                return _triangleIndicesCache;
            }
            set
            {
                _triangleIndicesCache = value;
            }
        }
 
                
        /// <summary>
        /// The material used to visually represent the Viewport2DVisual3D
        /// </summary>
        public static readonly DependencyProperty MaterialProperty = 
                                            DependencyProperty.Register("Material",
                                                           typeof(Material),
                                                           typeof(Viewport2DVisual3D),
                                                           new PropertyMetadata(null, 
                                                                                new PropertyChangedCallback(OnMaterialPropertyChanged)));
 
        /// <summary>
        ///     Material for this Viewport2DVisual3D.
        /// </summary>
        public Material Material
        {
            get { return (Material)GetValue(MaterialProperty); }
            set { SetValue(MaterialProperty, value); }
        }
 
        internal static void OnMaterialPropertyChanged(Object sender, DependencyPropertyChangedEventArgs e)
        {
            Viewport2DVisual3D viewport2DVisual3D = ((Viewport2DVisual3D)sender);
 
            viewport2DVisual3D.GenerateMaterial();            
        }
 
        /// <summary>
        /// The attached dependency property used to indicate whether a material should be made
        /// interactive.
        /// </summary>
        public static readonly DependencyProperty IsVisualHostMaterialProperty =
            DependencyProperty.RegisterAttached(
                "IsVisualHostMaterial",
                typeof(Boolean),
                typeof(Viewport2DVisual3D),
                new PropertyMetadata(BooleanBoxes.FalseBox));
 
        /// <summary>
        /// Sets the attached property IsVisualHostMaterial for the given element.
        /// </summary>
        /// <param name="element">The element to which to write the IsVisualHostMaterial attached property.</param>
        /// <param name="value">The value to set</param>
        public static void SetIsVisualHostMaterial(Material element, Boolean value)
        {
            // We should throw ArgumentNullException if element is null.
            element.SetValue(IsVisualHostMaterialProperty, BooleanBoxes.Box(value));
        }
 
        /// <summary>
        /// Reads the attached property IsVisualHostMaterial from the given element.
        /// </summary>
        /// <param name="element">The element from which to read the IsVisualHostMaterial attached property.</param>
        /// <returns>The property's value.</returns>
        public static Boolean GetIsVisualHostMaterial(Material element)
        {
 
            // We should throw ArgumentNullException if element is null.
            return (bool)element.GetValue(IsVisualHostMaterialProperty);
        }
 
 
        public static readonly DependencyProperty CacheModeProperty =
           DependencyProperty.Register(
                "CacheMode",
                typeof(CacheMode),
                typeof(Viewport2DVisual3D),
                new PropertyMetadata(null, new PropertyChangedCallback(OnCacheModeChanged)));
 
 
        public CacheMode CacheMode
        {
            get { return (CacheMode)GetValue(CacheModeProperty); }
            set { SetValue(CacheModeProperty, value); }
        }
 
        internal static void OnCacheModeChanged(Object sender, DependencyPropertyChangedEventArgs e)
        {
            Viewport2DVisual3D viewport2DVisual3D = ((Viewport2DVisual3D)sender);
 
            BitmapCache oldValue = (CacheMode)e.OldValue as BitmapCache;
            BitmapCache newValue = (CacheMode)e.NewValue as BitmapCache;
 
            if (oldValue != newValue)
            {
                viewport2DVisual3D.InternalBitmapCacheBrush.BitmapCache = newValue;
 
                //
                // The BitmapCacheBrush doesn't point directly at the Visual like the VisualBrush does,
                // since BitmapCacheBrush ignores most properties on a Visual by design.  In order for
                // those properties to be respected to match the internal VisualBrush's behavior, we insert
                // a dummy Visual node between the V2DV3D and its 2D Visual.  We then target the dummy
                // node with the brush instead.
                //
                
                if (oldValue == null)
                {
                    //
                    // If we are swapping from using the VisualBrush to using the BitmapCacheBrush...
                    //
 
                    // Remove the visual child from the V2DV3D and add the dummy child.
                    viewport2DVisual3D.RemoveVisualChild(viewport2DVisual3D.Visual);
                    viewport2DVisual3D.AddVisualChild(viewport2DVisual3D.InternalBitmapCacheBrush.InternalTarget);
 
                    Debug.Assert(  (viewport2DVisual3D.InternalBitmapCacheBrush.InternalTarget == null 
                                 || viewport2DVisual3D.InternalBitmapCacheBrush.InternalTarget.InheritanceContext == null), 
                                "Expected AddVisualChild to remove the InheritanceContext on InternalTarget");
 
                    // Swap the brush pointing to the visual.  The cache brush will re-parent the visual to the dummy.
                    viewport2DVisual3D.InternalVisualBrush.Visual = null;
                    viewport2DVisual3D.InternalBitmapCacheBrush.Target = viewport2DVisual3D.Visual;
 
                    // setting the cache brush to use this new child should not invalidate our previous condition
                    Debug.Assert(  (viewport2DVisual3D.InternalBitmapCacheBrush.InternalTarget == null 
                                 || viewport2DVisual3D.InternalBitmapCacheBrush.InternalTarget.InheritanceContext == null), 
                                 "Expected the InternalBitmapCacheBrush not to set the InheritanceContext");
                }
 
                if (newValue == null)
                {
                    //
                    // If we are swapping from using the BitmapCacheBrush to using the VisualBrush...
                    //
 
                    // Swap the brush pointing to the visual.  The cache brush will remove the dummy as the parent
                    // of the visual.
                    viewport2DVisual3D.InternalBitmapCacheBrush.Target = null;
                    viewport2DVisual3D.InternalVisualBrush.Visual = viewport2DVisual3D.Visual;
                    
                    // Remove the dummy child and re-add the visual as the V2DV3D's child.
                    viewport2DVisual3D.RemoveVisualChild(viewport2DVisual3D.InternalBitmapCacheBrush.InternalTarget);
                    viewport2DVisual3D.AddVisualChild(viewport2DVisual3D.Visual);
                    
                    Debug.Assert((viewport2DVisual3D.Visual == null || viewport2DVisual3D.Visual.InheritanceContext == null), 
                                 "Expected AddVisualChild to remove the InheritanceContext on Visual");
                }
 
                // If we changed from using one brush to the other we need to regenerate the Material.
                if (oldValue == null || newValue == null)
                {
                    viewport2DVisual3D.GenerateMaterial();
                }
            }
        }
        
        /// <summary>
        ///  Derived classes override this property to enable the Visual code to enumerate
        ///  the Visual children. Derived classes need to return the number of children
        ///  from this method.
        ///
        ///    By default a Visual does not have any children.
        ///
        ///  Remark: During this virtual method the Visual tree must not be modified.
        /// </summary>
        protected override int Visual3DChildrenCount
        {
            get { return 0; }
        }
 
        /// <summary>
        ///    Derived class must implement to support Visual children. The method must return
        ///    the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1.
        ///
        ///    By default a Visual3D does not have any children.
        ///
        ///  Remark:
        ///       Need to lock down Visual tree during the callbacks.
        ///       During this virtual call it is not valid to modify the Visual tree.
        ///
        ///       It is okay to type this protected API to the 2D Visual.  The only 2D Visual with
        ///       3D childern is the Viewport3DVisual which is sealed.
        /// </summary>
        protected override Visual3D GetVisual3DChild(int index)
        {
           throw new ArgumentOutOfRangeException("index", index, SR.Visual_ArgumentOutOfRange);
        }
 
        /// <summary>
        /// Returns the number of children of this object (in most cases this will be
        /// the number of Visuals, but it some cases, Viewport3DVisual for instance,
        /// this is the number of Visual3Ds).
        ///
        /// Used only by VisualTreeHelper.
        /// </summary>
        internal override int InternalVisual2DOr3DChildrenCount
        {
            get
            {
                // Call the right virtual method.
                return (Visual != null ? 1 : 0);
            }
        }
 
        /// <summary>
        /// Returns the child at index "index" (in most cases this will be
        /// a Visual, but it some cases, Viewport3DVisual for instance,
        /// this is a Visual3D).
        ///
        /// Used only by VisualTreeHelper.
        /// </summary>
        internal override DependencyObject InternalGet2DOr3DVisualChild(int index)
        {
            Visual visualChild = Visual;
 
            if (index != 0 || visualChild == null)
            {
                throw new ArgumentOutOfRangeException("index", index, SR.Visual_ArgumentOutOfRange);
            }
            
            return visualChild;
        }
 
        /// <summary>
        /// CachingHintProperty - Hints the rendering engine that rendered content should be cached
        /// when possible.
        /// </summary>
        private static readonly DependencyProperty CachingHintProperty =
            RenderOptions.CachingHintProperty.AddOwner(
                                        typeof(Viewport2DVisual3D), 
                                        new UIPropertyMetadata(
                                            new PropertyChangedCallback(OnCachingHintChanged)));
 
        private static void OnCachingHintChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Viewport2DVisual3D viewport2D = (Viewport2DVisual3D) d;
 
            RenderOptions.SetCachingHint(viewport2D._visualBrush, (CachingHint)e.NewValue);            
        }
 
        /// <summary>
        /// CacheInvalidationThresholdMinimum - 
        /// </summary>
        private static readonly DependencyProperty CacheInvalidationThresholdMinimumProperty =
            RenderOptions.CacheInvalidationThresholdMinimumProperty.AddOwner(
                                        typeof(Viewport2DVisual3D), 
                                        new UIPropertyMetadata(
                                            new PropertyChangedCallback(OnCacheInvalidationThresholdMinimumChanged)));
 
 
        private static void OnCacheInvalidationThresholdMinimumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Viewport2DVisual3D viewport2D = (Viewport2DVisual3D) d;
 
            RenderOptions.SetCacheInvalidationThresholdMinimum(viewport2D._visualBrush, (double)e.NewValue);            
        }
 
        /// <summary>
        /// CacheInvalidationThresholdMaximum - 
        /// </summary>
        private static readonly DependencyProperty CacheInvalidationThresholdMaximumProperty =
            RenderOptions.CacheInvalidationThresholdMaximumProperty.AddOwner(
                                        typeof(Viewport2DVisual3D), 
                                        new UIPropertyMetadata(
                                            new PropertyChangedCallback(OnCacheInvalidationThresholdMaximumChanged)));
 
        private static void OnCacheInvalidationThresholdMaximumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Viewport2DVisual3D viewport2D = (Viewport2DVisual3D) d;
 
            RenderOptions.SetCacheInvalidationThresholdMaximum(viewport2D._visualBrush, (double)e.NewValue);            
        }
             
        //------------------------------------------------------------------------
        //
        // PRIVATE DATA
        //
        //------------------------------------------------------------------------
        
        // the actual visual that is created        
        private VisualBrush _visualBrush;
        private BitmapCacheBrush _bitmapCacheBrush;
 
        private Point3DCollection _positionsCache = null;
        private PointCollection _textureCoordinatesCache = null;
        private Int32Collection _triangleIndicesCache = null;
    }
}