File: System\Windows\Media3D\Visual3D.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.Media;
using MS.Internal.Media3D;
using System;
using System.Diagnostics;
using System.Security;
using System.Windows.Diagnostics;
using System.Windows.Media.Composition;
using System.Windows.Media;
 
using SR=MS.Internal.PresentationCore.SR;
 
namespace System.Windows.Media.Media3D
{
    /// <summary>
    ///     Visual3D is the base class for all 3D visual elements.
    /// </summary>
    public abstract partial class Visual3D : DependencyObject, DUCE.IResource, IVisual3DContainer
    {
        // --------------------------------------------------------------------
        //
        //   Constants
        //
        // --------------------------------------------------------------------
 
        #region Constants
 
        /// <summary>
        /// This is the dirty mask for a visual, set every time we marshall
        /// a visual to a channel and reset by the end of the render pass.
        /// </summary>
        private const VisualProxyFlags c_Model3DVisualProxyFlagsDirtyMask =
              VisualProxyFlags.IsSubtreeDirtyForRender
            | VisualProxyFlags.IsContentDirty
            | VisualProxyFlags.IsTransformDirty;
 
        #endregion Constants
 
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        // Prevent 3rd parties from extending this abstract base class.
        internal Visual3D()
        {
            _internalIsVisible = true;
        }
 
        #endregion Constructors
 
 
        // --------------------------------------------------------------------
        //
        //   IResource implementation
        //
        // --------------------------------------------------------------------
 
        #region IResource implementation
 
        /// <summary>
        /// This is used to check if the composition node
        /// for the visual is on channel
        /// </summary>
        /// <param name="channel"></param>
        /// <returns></returns>
        internal bool IsOnChannel(DUCE.Channel channel)
        {
            return _proxy.IsOnChannel(channel);
        }
 
        /// <summary>
        /// Returns the handle this visual has on the given channel.
        /// </summary>
        /// <param name="channel"></param>
        /// <returns></returns>
        DUCE.ResourceHandle DUCE.IResource.GetHandle(DUCE.Channel channel)
        {
            return _proxy.GetHandle(channel);
        }
 
        /// <summary>
        /// Returns the handle this visual has on the given channel.
        /// Note: The 3D handle is the normal handle for Visual3D
        /// </summary>
        DUCE.ResourceHandle DUCE.IResource.Get3DHandle(DUCE.Channel channel)
        {
            return _proxy.GetHandle(channel);
        }
 
        /// <summary>
        /// This is used to create or addref the visual resource
        /// on the given channel
        /// </summary>
        /// <param name="channel"></param>
        /// <returns></returns>
        DUCE.ResourceHandle DUCE.IResource.AddRefOnChannel(DUCE.Channel channel)
        {
            _proxy.CreateOrAddRefOnChannel(this, channel, DUCE.ResourceType.TYPE_VISUAL3D);
 
            return _proxy.GetHandle(channel);
        }
 
        /// <summary>
        /// Sends a command to compositor to remove the child
        /// from its parent on the channel.
        /// </summary>
        void DUCE.IResource.RemoveChildFromParent(
                DUCE.IResource parent,
                DUCE.Channel channel)
        {
            DUCE.Visual3DNode.RemoveChild(
                parent.Get3DHandle(channel),
                _proxy.GetHandle(channel),
                channel);
        }
 
        int DUCE.IResource.GetChannelCount()
        {
            return _proxy.Count;
        }
 
        DUCE.Channel DUCE.IResource.GetChannel(int index)
        {
            return _proxy.GetChannel(index);
        }
 
        #endregion IResource implementation
 
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        #region Public Methods
 
        /// <summary>
        ///    DependencyProperty which backs the ModelVisual3D.Transform property.
        /// </summary>
        public static readonly DependencyProperty TransformProperty =
            DependencyProperty.Register(
                    "Transform",
                    /* propertyType = */ typeof(Transform3D),
                    /* ownerType = */ typeof(Visual3D),
                    new PropertyMetadata(Transform3D.Identity, TransformPropertyChanged),
                    (ValidateValueCallback) delegate { return MediaContext.CurrentMediaContext.WriteAccessEnabled; });
 
        private static void TransformPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Visual3D owner = ((Visual3D) d);
 
            if (!e.IsASubPropertyChange)
            {
                if (e.OldValue != null)
                {
                    owner.DisconnectAttachedResource(
                        VisualProxyFlags.IsTransformDirty,
                        ((DUCE.IResource) e.OldValue));
                }
 
                owner.SetFlagsOnAllChannels(true, VisualProxyFlags.IsTransformDirty);
            }
 
            // Stop over-invalidating _bboxSubgraph
            //
            // We currently maintain a cache of both a ModelVisual3D’s content
            // and subgraph bounds.  A better solution that would be both a 2D
            // and 3D win would be to stop invalidating _bboxSubgraph when a
            // visual’s transform changes.
            owner.RenderChanged(/* sender = */ owner, EventArgs.Empty);
        }
 
        /// <summary>
        ///     Transform for this Visual3D.
        /// </summary>
        public Transform3D Transform
        {
            get
            {
                return (Transform3D) GetValue(TransformProperty);
            }
 
            set
            {
                SetValue(TransformProperty, value);
            }
        }
 
        #endregion Public Methods
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Public Events
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
 
        #region Protected Methods
 
        /// <summary>
        /// AttachChild
        ///
        ///    Derived classes must call this method to notify the Visual3D layer that a new
        ///    child appeard in the children collection. The Visual3D layer will then call the GetVisual3DChild
        ///    method to find out where the child was added.
        ///
        ///  Remark: To move a Visual3D child in a collection it must be first disconnected and then connected
        ///    again. (Moving forward we might want to add a special optimization there so that we do not
        ///    unmarshal our composition resources).
        /// </summary>
        protected void AddVisual3DChild(Visual3D child)
        {
            // It is invalid to modify the children collection that we
            // might be iterating during a property invalidation tree walk.
            if (IsVisualChildrenIterationInProgress)
            {
                throw new InvalidOperationException(SR.CannotModifyVisualChildrenDuringTreeWalk);
            }
 
            // invalid during a VisualTreeChanged event
            VisualDiagnostics.VerifyVisualTreeChange(this);
 
            Debug.Assert(child != null);
            Debug.Assert(child.InternalVisualParent == null);
 
            child.SetParent(this);
 
            // set the inheritance context so databinding, etc... work
            ProvideSelfAsInheritanceContext(child, null);
 
            // The child already might be dirty. Hence we need to propagate dirty information
            // from the parent and from the child.
            Visual3D.PropagateFlags(
                this,
                VisualFlags.IsSubtreeDirtyForPrecompute,
                VisualProxyFlags.IsSubtreeDirtyForRender);
 
            Visual3D.PropagateFlags(
                child,
                VisualFlags.IsSubtreeDirtyForPrecompute,
                VisualProxyFlags.IsSubtreeDirtyForRender);
 
            // We do not currently support layout in 3D
            // UIElement.PropagateResumeLayout(child);
 
            // Fire notifications
            OnVisualChildrenChanged(child, /* visualRemoved = */ null);
 
            child.FireOnVisualParentChanged(null);
            VisualDiagnostics.OnVisualChildChanged(this, child, true);
        }
 
 
        /// <summary>
        /// DisconnectChild
        ///
        ///    Derived classes must call this method to notify the Visual3D layer that a
        ///    child was removed from the children collection. The Visual3D layer will then call
        ///    GetVisual3DChild to find out which child has been removed.
        ///
        /// </summary>
        protected void RemoveVisual3DChild(Visual3D child)
        {
            // It is invalid to modify the children collection that we
            // might be iterating during a property invalidation tree walk.
            if (IsVisualChildrenIterationInProgress)
            {
                throw new InvalidOperationException(SR.CannotModifyVisualChildrenDuringTreeWalk);
            }
 
            Debug.Assert(child != null);
            Debug.Assert(child.InternalVisualParent == this);
 
            // invalid during a VisualTreeChanged event
            VisualDiagnostics.VerifyVisualTreeChange(this);
 
            VisualDiagnostics.OnVisualChildChanged(this, child, false);
 
            child.SetParent(/* newParent = */ (Visual3D) null);  // CS0121: Call is ambigious without casting null to Visual3D.
 
            // remove the inheritance context
            RemoveSelfAsInheritanceContext(child, null);
 
            //
            // Remove the child on all the channels
            // this visual is being marshalled to.
            //
 
            for (int i = 0, limit = _proxy.Count; i < limit; i++)
            {
                DUCE.Channel channel = _proxy.GetChannel(i);
 
                if (child.CheckFlagsAnd(channel, VisualProxyFlags.IsConnectedToParent))
                {
                    child.SetFlags(channel, false, VisualProxyFlags.IsConnectedToParent);
                    DUCE.IResource childResource = (DUCE.IResource)child;
                    childResource.RemoveChildFromParent(this, channel);
                    childResource.ReleaseOnChannel(channel);
                }
            }
 
            //
            // Force a full precompute and render pass for this visual.
            //
 
            Visual3D.PropagateFlags(
                this,
                VisualFlags.IsSubtreeDirtyForPrecompute,
                VisualProxyFlags.IsSubtreeDirtyForRender);
 
            // We do not currently support layout in 3D
            // UIElement.PropagateSuspendLayout(child);
 
            // Fire notifications
            child.FireOnVisualParentChanged(this);
 
            OnVisualChildrenChanged(/* visualAdded = */ null , child);
        }
 
        /// <summary>
        ///    The InternalIsVisible property roughly corresponds to the Opacity in the Visual world.
        ///    When it is set to true, then the actual transform used for this visual on the MIL side
        ///    is set to a zero scale transform.  This makes the object invisible.
        /// </summary>
        internal bool InternalIsVisible
        {
            get
            {
                return _internalIsVisible;
            }
            set
            {
                if (_internalIsVisible != value)
                {
                    // if we're going from Not Visible -> Visibile remove the zero scale from the Channel
                    // otherwise remove the user's transform
                    if (value)
                    {
                        DisconnectAttachedResource(
                            VisualProxyFlags.IsTransformDirty,
                            ((DUCE.IResource)_zeroScale));
                    }
                    else
                    {
                        Transform3D transform = Transform;
 
                        if (transform != null)
                        {
                            DisconnectAttachedResource(
                                VisualProxyFlags.IsTransformDirty,
                                ((DUCE.IResource)transform));
                        }
                    }
 
                    SetFlagsOnAllChannels(true, VisualProxyFlags.IsTransformDirty);
 
                    RenderChanged(/* sender = */ this, EventArgs.Empty);
 
                    _internalIsVisible = value;
                }
            }
        }
 
        // isSubpropertyChange indicates whether a subproperty on Visual3DModel changed.
        // In this case oldValue is ignored, since the value hasn't changed.  If it isn't a
        // sub property change, then oldValue gives the previous value of the Visual3DModel property.
        private void Visual3DModelPropertyChanged(Model3D oldValue, bool isSubpropertyChange)
        {
            if (!isSubpropertyChange)
            {
                if (oldValue != null)
                {
                    DisconnectAttachedResource(VisualProxyFlags.IsContentDirty,
                                               oldValue);
                }
 
                SetFlagsOnAllChannels(true, VisualProxyFlags.IsContentDirty);
            }
 
            SetFlags(false, VisualFlags.Are3DContentBoundsValid);
 
            RenderChanged(/* sender = */ this, EventArgs.Empty);
        }
 
        private void Visual3DModelPropertyChanged(object o, EventArgs e)
        {
            // forward on to the main property changed method.  Since this method is
            // only called on subproperty chanes, oldValue is meaningless.
            Visual3DModelPropertyChanged(null, /* isSubpropertyChange = */ true);
        }
 
        /// <summary>
        ///     The Model3D to render
        /// </summary>
        protected Model3D Visual3DModel
        {
            get
            {
                VerifyAPIReadOnly();
 
                return _visual3DModel;
            }
 
            set
            {
                VerifyAPIReadWrite();
 
                if (value != _visual3DModel)
                {
                    // remove the old change listener
                    if (_visual3DModel != null && !_visual3DModel.IsFrozenInternal)
                    {
                        _visual3DModel.ChangedInternal -= Visual3DModelPropertyChanged;
                    }
 
                    // notify of the property change
                    Visual3DModelPropertyChanged(_visual3DModel, /* isSubpropertyChange = */ false);
                    _visual3DModel = value;
 
                    // set the new one
                    if (_visual3DModel != null && !_visual3DModel.IsFrozenInternal)
                    {
                        _visual3DModel.ChangedInternal += Visual3DModelPropertyChanged;
                    }
                }
            }
        }
 
        /// <summary>
        /// This is called when the parent link of the Visual is changed.
        /// This method executes important base functionality before calling the
        /// overridable virtual.
        /// </summary>
        /// <param name="oldParent">Old parent or null if the Visual did not have a parent before.</param>
        internal virtual void FireOnVisualParentChanged(DependencyObject oldParent)
        {
            // Call the ParentChanged virtual before firing the Ancestor Changed Event
            OnVisualParentChanged(oldParent);
 
            // If we are attaching to a tree then
            // send the bit up if we need to.
            if (oldParent == null)
            {
                Debug.Assert(VisualTreeHelper.GetParent(this) != null, "If oldParent is null, current parent should != null.");
 
                if(CheckFlagsAnd(VisualFlags.SubTreeHoldsAncestorChanged))
                {
                    Visual.SetTreeBits(
                        VisualTreeHelper.GetParent(this),
                        VisualFlags.SubTreeHoldsAncestorChanged,
                        VisualFlags.RegisteredForAncestorChanged);
                }
            }
            // If we are cutting a sub tree off then
            // clear the bit in the main tree above if we need to.
            else
            {
                if (CheckFlagsAnd(VisualFlags.SubTreeHoldsAncestorChanged))
                {
                    Visual.ClearTreeBits(
                        oldParent,
                        VisualFlags.SubTreeHoldsAncestorChanged,
                        VisualFlags.RegisteredForAncestorChanged);
                }
            }
 
            // Fire the Ancestor changed Event on the nodes.
            AncestorChangedEventArgs args = new AncestorChangedEventArgs(this, oldParent);
            ProcessAncestorChangedNotificationRecursive(this, args);
        }
 
        /// <summary>
        ///   Add removed delegates to the VisualAncenstorChanged Event.
        /// </summary>
        /// <remarks>
        ///     This also sets/clears the tree-searching bit up the tree
        /// </remarks>
        internal event Visual.AncestorChangedEventHandler VisualAncestorChanged
        {
            add
            {
                Visual.AncestorChangedEventHandler newHandler = AncestorChangedEventField.GetValue(this);
 
                if (newHandler == null)
                {
                    newHandler = value;
                }
                else
                {
                    newHandler += value;
                }
 
                AncestorChangedEventField.SetValue(this, newHandler);
 
                Visual.SetTreeBits(
                    this,
                    VisualFlags.SubTreeHoldsAncestorChanged,
                    VisualFlags.RegisteredForAncestorChanged);
            }
 
            remove
            {
                // check that we are Disabling a node that was previously Enabled
                if(CheckFlagsAnd(VisualFlags.SubTreeHoldsAncestorChanged))
                {
                    Visual.ClearTreeBits(
                                        this,
                                        VisualFlags.SubTreeHoldsAncestorChanged,
                                        VisualFlags.RegisteredForAncestorChanged);
                }
 
                // if we are Disabling a Visual that was not Enabled then this
                // search should fail.  But it is safe to check.
                Visual.AncestorChangedEventHandler newHandler = AncestorChangedEventField.GetValue(this);
 
                if (newHandler != null)
                {
                    newHandler -= value;
 
                    if(newHandler == null)
                    {
                        AncestorChangedEventField.ClearValue(this);
                    }
                    else
                    {
                        AncestorChangedEventField.SetValue(this, newHandler);
                    }
                }
            }
        }
 
        /// <summary>
        ///     Walks down in the tree for nodes that have AncestorChanged Handlers
        ///     registered and calls them.
        ///     It uses Flag bits that help it prune the walk.  This should go
        ///     straight to the relevent nodes.
        /// </summary>
        internal static void ProcessAncestorChangedNotificationRecursive(DependencyObject e, AncestorChangedEventArgs args)
        {
            if (e is Visual)
            {
                Visual.ProcessAncestorChangedNotificationRecursive(e, args);
            }
            else
            {
                Visual3D eAsVisual3D = e as Visual3D;
 
                // If the flag is not set, then we are Done.
                if(!eAsVisual3D.CheckFlagsAnd(VisualFlags.SubTreeHoldsAncestorChanged))
                {
                    return;
                }
 
                // If there is a handler on this node, then fire it.
                Visual.AncestorChangedEventHandler handler = AncestorChangedEventField.GetValue(eAsVisual3D);
 
                if(handler != null)
                {
                    handler(eAsVisual3D, args);
                }
 
                // Decend into the children.
                int count = eAsVisual3D.InternalVisual2DOr3DChildrenCount;
                for (int i = 0; i < count; i++)
                {
                    DependencyObject child = eAsVisual3D.InternalGet2DOr3DVisualChild(i);
                    if (child != null)
                    {
                        Visual3D.ProcessAncestorChangedNotificationRecursive(child, args);
                    }
                }
            }
        }
 
 
        /// <summary>
        /// OnVisualParentChanged is called when the parent of the Visual is changed.
        /// </summary>
        /// <param name="oldParent">Old parent or null if the Visual did not have a parent before.</param>
        protected internal virtual void OnVisualParentChanged(DependencyObject oldParent)
        {
        }
 
        /// <summary>
        /// OnVisualChildrenChanged is called when the VisualCollection of the Visual is edited.
        /// </summary>
        protected internal virtual void OnVisualChildrenChanged(
            DependencyObject visualAdded,
            DependencyObject visualRemoved)
        {
        }
 
        #endregion Protected Methods
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        internal bool DoesRayHitSubgraphBounds(RayHitTestParameters rayParams)
        {
            Point3D origin;
            Vector3D direction;
            rayParams.GetLocalLine(out origin, out direction);
 
            Rect3D bboxSubgraph = VisualDescendantBounds;
            return LineUtil.ComputeLineBoxIntersection(ref origin, ref direction, ref bboxSubgraph, rayParams.IsRay);
        }
 
        /// <summary>
        /// Initiate a hit test using delegates.
        /// </summary>
        internal void HitTest(
            HitTestFilterCallback filterCallback,
            HitTestResultCallback resultCallback,
            HitTestParameters3D hitTestParameters)
        {
            ArgumentNullException.ThrowIfNull(resultCallback);
 
            ArgumentNullException.ThrowIfNull(hitTestParameters);
 
            VerifyAPIReadWrite();
 
            RayHitTestParameters rayParams = hitTestParameters as RayHitTestParameters;
 
            if (rayParams != null)
            {
                // In case the user is reusing the same RayHitTestParameters
                rayParams.ClearResults();
 
                HitTestResultBehavior result = RayHitTest(filterCallback, rayParams);
 
                rayParams.RaiseCallback(resultCallback, filterCallback, result);
            }
            else
            {
                // This should never happen, users can not extend the abstract HitTestParameters3D class.
                Invariant.Assert(false,
                    String.Format(System.Globalization.CultureInfo.InvariantCulture,
                        "'{0}' HitTestParameters3D are not supported on {1}.",
                        hitTestParameters.GetType().Name, this.GetType().Name));
            }
        }
 
        internal HitTestResultBehavior RayHitTest(
            HitTestFilterCallback filterCallback,
            RayHitTestParameters rayParams)
        {
            if (DoesRayHitSubgraphBounds(rayParams))
            {
                //
                // Determine if there is a special filter behavior defined for this
                // Visual.
                //
 
                HitTestFilterBehavior behavior = HitTestFilterBehavior.Continue;
 
                if (filterCallback != null)
                {
                    behavior = filterCallback(this);
 
                    if (HTFBInterpreter.SkipSubgraph(behavior)) return HitTestResultBehavior.Continue;
                    if (HTFBInterpreter.Stop(behavior)) return HitTestResultBehavior.Stop;
                }
 
                //
                // Hit test against the children.
                //
 
                if (HTFBInterpreter.IncludeChildren(behavior))
                {
                    HitTestResultBehavior result = HitTestChildren(filterCallback, rayParams);
 
                    if (result == HitTestResultBehavior.Stop) return HitTestResultBehavior.Stop;
                }
 
                //
                // Hit test against the content of this Visual.
                //
 
                if (HTFBInterpreter.DoHitTest(behavior))
                {
                    RayHitTestInternal(filterCallback, rayParams);
                }
            }
 
            return HitTestResultBehavior.Continue;
        }
 
        internal HitTestResultBehavior HitTestChildren(
            HitTestFilterCallback filterCallback,
            RayHitTestParameters rayParams)
        {
            return HitTestChildren(filterCallback, rayParams, this);
        }
 
        /// <summary>
        ///     Static helper used by ModelVisual3D and Viewport3DVisual to hit test
        ///     against their children collections.
        /// </summary>
        internal static HitTestResultBehavior HitTestChildren(
            HitTestFilterCallback filterCallback,
            RayHitTestParameters rayParams,
            IVisual3DContainer container)
        {
            if (container != null)
            {
                int childrenCount = container.GetChildrenCount();
 
                for (int i = childrenCount - 1; i >= 0; i--)
                {
                    Visual3D child = container.GetChild(i);
 
                    // Visuall3D.RayHitTest does not apply the Visual3D's Transform.  We need to
                    // transform into the content's space before hit testing.
                    Transform3D transform = child.Transform;
                    rayParams.PushVisualTransform(transform);
 
                    // Perform the hit-test against the child.
                    HitTestResultBehavior result = child.RayHitTest(filterCallback, rayParams);
 
                    rayParams.PopTransform(transform);
 
                    if (result == HitTestResultBehavior.Stop)
                    {
                        return HitTestResultBehavior.Stop;
                    }
                }
            }
 
            return HitTestResultBehavior.Continue;
        }
 
        internal void RayHitTestInternal(
            HitTestFilterCallback filterCallback,
            RayHitTestParameters rayParams)
        {
            Model3D model = _visual3DModel;
 
            if (model != null)
            {
                // If our Model3D hit test intersects anything we should return "this" Visual3D
                // as the HitTestResult.VisualHit.
                rayParams.CurrentVisual = this;
 
                model.RayHitTest(rayParams);
            }
        }
 
        /// <summary>
        ///     Generic "render changed" handler which sets IsDirtyForRender
        ///     and propagates IsSubtreeDirtyForRender/Precompute.
        /// </summary>
        internal void RenderChanged(object sender, EventArgs e)
        {
            PropagateFlags(
                this,
                VisualFlags.IsSubtreeDirtyForPrecompute,
                VisualProxyFlags.IsSubtreeDirtyForRender);
        }
 
        /// <summary>
        /// VisualContentBounds returns the bounding box for the contents of the current visual.
        /// </summary>
        internal Rect3D VisualContentBounds
        {
            get
            {
                // Probably too restrictive. Let's see who wants it during OnRender.
                VerifyAPIReadWrite();
 
                return GetContentBounds();
            }
        }
 
        /// <summary>
        /// Visual2DContentBounds returns the 2D bounding box for the content of this 3D object.  The 2D bounding box
        /// is the projection of the 3D content bounding box up to the nearest 2D visual that contains the Visual3D.
        /// </summary>
        internal Rect Visual2DContentBounds
        {
            get
            {
                VerifyAPIReadWrite();
                Rect contentBounds = Rect.Empty;
 
                Viewport3DVisual viewport3DVisual = (Viewport3DVisual)VisualTreeHelper.GetContainingVisual2D(this);
                if (viewport3DVisual != null) {
                    GeneralTransform3DTo2D transform = TransformToAncestor(viewport3DVisual);
                    contentBounds = transform.TransformBounds(VisualContentBounds);
                }
 
                return contentBounds;
            }
        }
 
        internal Rect3D BBoxSubgraph
        {
            get
            {
                if (CheckFlagsAnd(VisualFlags.IsSubtreeDirtyForPrecompute))
                {
                    // force an update of the bounds cache
                    Rect3D transformedBBoxSubgraphIgnored;
                    PrecomputeRecursive(out transformedBBoxSubgraphIgnored);
                }
 
                Debug_VerifyCachedSubgraphBounds();
 
                return _bboxSubgraph;
            }
 
        }
 
        /// <summary>
        /// Derived classes must override this method and return the bounding
        /// box of their content in the Visual's local space.
        /// </summary>
        internal Rect3D GetContentBounds()
        {
            Model3D model = _visual3DModel;
 
            if (model == null)
            {
                return Rect3D.Empty;
            }
 
            if (!CheckFlagsAnd(VisualFlags.Are3DContentBoundsValid))
            {
                _bboxContent = model.CalculateSubgraphBoundsOuterSpace();
                SetFlags(true, VisualFlags.Are3DContentBoundsValid);
            }
 
            Debug_VerifyCachedContentBounds();
 
            return _bboxContent;
        }
 
        /// <summary>
        /// Returns the subgraph bounds in the Visual3D's outer coordinate space.
        /// </summary>
        internal Rect3D CalculateSubgraphBoundsOuterSpace()
        {
            Rect3D bounds = CalculateSubgraphBoundsInnerSpace();
 
            return M3DUtil.ComputeTransformedAxisAlignedBoundingBox(ref bounds, Transform);
        }
 
        /// <summary>
        /// Returns the subgraph bounds in the Visual3D's inner coordinate space.
        /// </summary>
        internal Rect3D CalculateSubgraphBoundsInnerSpace()
        {
            return BBoxSubgraph;
        }
 
        /// <summary>
        /// VisualDescendantBounds returns the union of all of the content bounding
        /// boxes for all of the descendants of the current visual, but not including
        /// the contents of the current visual.
        /// </summary>
        internal Rect3D VisualDescendantBounds
        {
            get
            {
                // Probably too restrictive. Let's see who wants it during OnRender.
                VerifyAPIReadWrite();
 
                return CalculateSubgraphBoundsInnerSpace();
            }
        }
 
        // CS0536: Intreface implementation must be public or explicit so we re-expose
        // the internal methods as an explicit implementation of an internal interface.
        void IVisual3DContainer.VerifyAPIReadOnly() { this.VerifyAPIReadOnly(); }
        void IVisual3DContainer.VerifyAPIReadOnly(DependencyObject other) { this.VerifyAPIReadOnly(other); }
        void IVisual3DContainer.VerifyAPIReadWrite() { this.VerifyAPIReadWrite(); }
        void IVisual3DContainer.VerifyAPIReadWrite(DependencyObject other) { this.VerifyAPIReadWrite(other); }
 
        /// <summary>
        /// Applies various API checks
        /// </summary>
        internal void VerifyAPIReadOnly()
        {
            // Verify that we are executing on the right context
            VerifyAccess();
        }
 
        /// <summary>
        /// Applies various API checks
        /// </summary>
        internal void VerifyAPIReadOnly(DependencyObject other)
        {
            VerifyAPIReadOnly();
 
            if (other != null)
            {
                // Make sure the visual is on the same context as we are
                MediaSystem.AssertSameContext(this, other);
            }
        }
 
        /// <summary>
        /// Applies various API checks for read/write
        /// </summary>
        internal void VerifyAPIReadWrite()
        {
            VerifyAPIReadOnly();
 
            // Verify the correct access permissions
            MediaContext.From(this.Dispatcher).VerifyWriteAccess();
        }
 
        /// <summary>
        /// Applies various API checks
        /// </summary>
        internal void VerifyAPIReadWrite(DependencyObject other)
        {
            VerifyAPIReadWrite();
 
            if (other != null)
            {
                // Make sure the visual is on the same context as we are
                MediaSystem.AssertSameContext(this, other);
            }
        }
 
        internal void SetParent(Visual newParent)
        {
            _2DParent.SetValue(this, newParent);
            _3DParent = null;
 
            Debug.Assert(InternalVisualParent == newParent);
        }
 
        internal void SetParent(Visual3D newParent)
        {
            _2DParent.ClearValue(this);
            _3DParent = newParent;
 
            Debug.Assert(InternalVisualParent == newParent);
        }
 
        /// <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 virtual 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 Visual 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 virtual Visual3D GetVisual3DChild(int index)
        {
           throw new ArgumentOutOfRangeException("index", index, SR.Visual_ArgumentOutOfRange);
        }
 
        /// <summary>
        ///     Notifies the element that you have added a child.  The Element
        ///     will update the parent pointer, fire the correct events, etc.
        /// </summary>
        void IVisual3DContainer.AddChild(Visual3D child)
        {
            AddVisual3DChild(child);
        }
 
        /// <summary>
        ///     Notifies the element that you have removed a child.  The Element
        ///     will update the parent pointer, fire the correct events, etc.
        /// </summary>
        void IVisual3DContainer.RemoveChild(Visual3D child)
        {
            RemoveVisual3DChild(child);
        }
 
        /// <summary>
        ///     Gets the number of Visual3D children that the IVisual3DContainer
        ///     contains.
        /// </summary>
        int IVisual3DContainer.GetChildrenCount()
        {
            return Visual3DChildrenCount;
        }
 
        /// <summary>
        ///     Gets the index children of the IVisual3DContainer
        /// </summary>
        Visual3D IVisual3DContainer.GetChild(int index)
        {
            return GetVisual3DChild(index);
        }
 
        #region ForceInherit property support
 
        internal virtual void InvalidateForceInheritPropertyOnChildren(DependencyProperty property)
        {
            UIElement3D.InvalidateForceInheritPropertyOnChildren(this, property);
        }
 
        #endregion ForceInherit property support
 
        //------------------------------------------------------
        //
        //  DEBUG
        //
        //------------------------------------------------------
 
        #region DEBUG
 
        [Conditional("DEBUG")]
        internal void Debug_VerifyBoundsEqual(Rect3D bounds1, Rect3D bounds2, string errorString)
        {
            // The funny boolean logic below avoids asserts when the cached
            // bounds contain NaNs.  (NaN != NaN)
            bool boundsAreEqual =
                !(bounds1.X < bounds2.X || bounds1.X > bounds2.X) &&
                !(bounds1.Y < bounds2.Y || bounds1.Y > bounds2.Y) &&
                !(bounds1.Z < bounds2.Z || bounds1.Z > bounds2.Z) &&
                !(bounds1.SizeX < bounds2.SizeX || bounds1.SizeX > bounds2.SizeX) &&
                !(bounds1.SizeY < bounds2.SizeY || bounds1.SizeY > bounds2.SizeY) &&
                !(bounds1.SizeZ < bounds2.SizeZ || bounds1.SizeZ > bounds2.SizeZ);
 
            Debug.Assert(boundsAreEqual, errorString);
        }
 
        [Conditional("DEBUG")]
        internal void Debug_VerifyCachedSubgraphBounds()
        {
            Rect3D currentBounds = Rect3D.Empty;
#if DEBUG
            currentBounds = Debug_CalculateSubgraphBounds();
#endif
            // currentBounds includes Transform so we need to
            // temporarily transform _bboxSubgraph
            Rect3D cachedBounds = M3DUtil.ComputeTransformedAxisAlignedBoundingBox(ref _bboxSubgraph, Transform);
 
            Debug_VerifyBoundsEqual(cachedBounds, currentBounds, "Cached bbox subgraph is incorrect!");
        }
 
        [Conditional("DEBUG")]
        internal void Debug_VerifyCachedContentBounds()
        {
            Model3D model = _visual3DModel;
 
            Debug.Assert(model != null);
 
            Debug_VerifyBoundsEqual(model.CalculateSubgraphBoundsOuterSpace(),
                                    _bboxContent, "Cached content bounds is incorrect!");
        }
 
// [Conditional] does not work on methods that return values
#if DEBUG
        internal Rect3D Debug_CalculateSubgraphBounds()
        {
            Rect3D currentSubgraphBounds = GetContentBounds();
 
            for (int i = 0, count = Visual3DChildrenCount; i < count; i++)
            {
                currentSubgraphBounds.Union(
                    GetVisual3DChild(i).Debug_CalculateSubgraphBounds()
                    );
            }
 
            return M3DUtil.ComputeTransformedAxisAlignedBoundingBox(ref currentSubgraphBounds, Transform);
        }
#endif
 
        #endregion DEBUG
 
        /// <summary>
        /// Precompute pass.
        /// </summary>
        internal void PrecomputeRecursive(out Rect3D bboxSubgraph)
        {
            if (CheckFlagsAnd(VisualFlags.IsSubtreeDirtyForPrecompute))
            {
                //
                // Update the subgraph bounding box which includes the content bounds
                // and the bounds of our children.
                //
 
                _bboxSubgraph = GetContentBounds();
 
                for (int i = 0, count = Visual3DChildrenCount; i < count; i++)
                {
                    Visual3D child = GetVisual3DChild(i);
 
                    Rect3D bboxSubgraphChild;
                    child.PrecomputeRecursive(out bboxSubgraphChild);
                    _bboxSubgraph.Union(bboxSubgraphChild);
                }
 
                SetFlags(false, VisualFlags.IsSubtreeDirtyForPrecompute);
            }
 
            bboxSubgraph = M3DUtil.ComputeTransformedAxisAlignedBoundingBox(ref _bboxSubgraph, Transform);
       }
 
        internal void RenderRecursive(RenderContext ctx)
        {
            DUCE.Channel channel = ctx.Channel;
            DUCE.ResourceHandle handle = DUCE.ResourceHandle.Null;
            VisualProxyFlags flags = c_Model3DVisualProxyFlagsDirtyMask;
 
            //
            // Ensure that the resource for this Visual is sent to our current channel.
            //
            bool isOnChannel = IsOnChannel(channel);
            if (isOnChannel)
            {
                //
                // Good, we're already on channel. Get the handle and the flags.
                //
 
                handle = _proxy.GetHandle(channel);
                flags = _proxy.GetFlags(channel);
            }
            else
            {
                //
                // Create the visual resource on the current channel.
                //
                // Note: we need to update all set properties (the dirty
                //       bit mask is set by default).
                //
 
                handle = ((DUCE.IResource)this).AddRefOnChannel(channel);
            }
 
            //
            // Hookup content to the Visual
            //
 
            if ((flags & VisualProxyFlags.IsContentDirty) != 0)
            {
                RenderContent(ctx, isOnChannel);
            }
 
            //
            // Update the transform.
            //
 
            if ((flags & VisualProxyFlags.IsTransformDirty) != 0)
            {
                Transform3D transform = Transform;
                if (transform != null && InternalIsVisible)
                {
                    //
                    // Set the new transform resource for this visual.  If transform is
                    // null we don't need to do this.  Also note that the old transform
                    // was disconnected in the Transform property setter.
                    //
 
                    DUCE.Visual3DNode.SetTransform(
                        handle,
                        ((DUCE.IResource)transform).AddRefOnChannel(channel),
                        channel);
                }
                else if (!InternalIsVisible)
                {
                    DUCE.Visual3DNode.SetTransform(
                        handle,
                        ((DUCE.IResource)_zeroScale).AddRefOnChannel(channel),
                        channel);
                }
                else if (!isOnChannel) /* Transform == null */
                {
                    DUCE.Visual3DNode.SetTransform(
                        handle,
                        DUCE.ResourceHandle.Null,
                        channel);
                }
            }
 
            // Visit children of this node -----------------------------------------------------------------------
 
            Debug.Assert((flags & VisualProxyFlags.IsContentNodeConnected) == 0,
                "Only HostVisuals are expected to have a content node.");
 
            for (int i = 0; i < Visual3DChildrenCount; i++)
            {
                Visual3D child = GetVisual3DChild(i);
 
                if (child != null)
                {
                    if (child.CheckFlagsAnd(channel, VisualProxyFlags.IsSubtreeDirtyForRender) || // or the visual is dirty
                        !(child.IsOnChannel(channel))) // or the child has not been marshalled yet.
                    {
                        child.RenderRecursive(ctx);
                    }
 
                    if (child.IsOnChannel(ctx.Channel))
                    {
                        if (!child.CheckFlagsAnd(channel, VisualProxyFlags.IsConnectedToParent))
                        {
                            DUCE.Visual3DNode.InsertChildAt(
                                handle,
                                ((DUCE.IResource)child).GetHandle(channel),
                                /* iPosition = */ (uint)i,
                                ctx.Channel);
 
                            child.SetFlags(channel, true, VisualProxyFlags.IsConnectedToParent);
                        }
                    }
                }
            }
 
            //
            // Finally, reset the dirty flags for this visual (at this point,
            // we have handled them all).
            //
 
            SetFlags(channel, false, c_Model3DVisualProxyFlagsDirtyMask);
        }
 
        /// <summary>
        /// RenderContent is implemented to hook up the Visual3Ds content.
        /// The implementer of this function can assert that the _hCompNode
        /// is valid on a channel when the function is executed.
        /// </summary>
        internal void RenderContent(RenderContext ctx, bool isOnChannel)
        {
            DUCE.Channel channel = ctx.Channel;
 
            Debug.Assert(!CheckFlagsAnd(channel, VisualProxyFlags.IsContentConnected));
            Debug.Assert(IsOnChannel(channel));
 
            //
            // Create the content on the channel.
            //
 
            if (_visual3DModel != null)
            {
                //
                // Attach the content to the visual resource
                //
 
                DUCE.Visual3DNode.SetContent(
                    ((DUCE.IResource)this).GetHandle(channel),
                    ((DUCE.IResource)_visual3DModel).AddRefOnChannel(channel),
                    channel);
 
                SetFlags(channel, true, VisualProxyFlags.IsContentConnected);
            }
            else if (isOnChannel) /* Model == null */
            {
                DUCE.Visual3DNode.SetContent(
                    ((DUCE.IResource)this).GetHandle(channel),
                    DUCE.ResourceHandle.Null,
                    channel);
            }
        }
 
        /// <summary>
        /// Returns true if the specified ancestor is really the ancestor of the
        /// given descendant.
        /// </summary>
        public bool IsAncestorOf(DependencyObject descendant)
        {
            Visual visual;
            Visual3D visual3D;
 
            VisualTreeUtils.AsNonNullVisual(descendant, out visual, out visual3D);
 
            // x86 branch prediction skips the branch on first encounter.  We favor 3D.
            if(visual != null)
            {
                return visual.IsDescendantOf(this);
            }
 
            return visual3D.IsDescendantOf(this);
        }
 
        /// <summary>
        /// Returns true if the refernece Visual (this) is a descendant of the argument Visual.
        /// </summary>
        public bool IsDescendantOf(DependencyObject ancestor)
        {
            ArgumentNullException.ThrowIfNull(ancestor);
 
            VisualTreeUtils.EnsureVisual(ancestor);
 
            // Walk up the parent chain of the descendant untill we run out
            // of 3D parents or we find the ancestor.
            Visual3D current = this;
 
            while (current != null && current != ancestor)
            {
                // If our 3D parent is null then continue walk in 2D
                if (current._3DParent == null)
                {
                    DependencyObject parent2D = current.InternalVisualParent;
 
                    if (parent2D != null)
                    {
                        // The type has to be Visual because of the above if condition
                        return ((Visual)parent2D).IsDescendantOf(ancestor);
                    }
                }
 
                current = current._3DParent;
            }
 
            return current == ancestor;
        }
 
        /// <summary>
        ///     Walks up the Visual tree setting or clearing the given flags.  Unlike
        ///     PropagateFlags this does not terminate when it reaches node with
        ///     the flags already set.  It always walks all the way to the root.
        /// </summary>
        internal void SetFlagsToRoot(bool value, VisualFlags flag)
        {
            Visual3D current = this;
 
            do
            {
                current.SetFlags(value, flag);
 
                if (current._3DParent == null)
                {
                    VisualTreeUtils.SetFlagsToRoot(InternalVisualParent, value, flag);
                    return;
                }
 
                current = current._3DParent;
            }
            while (current != null);
        }
 
        /// <summary>
        ///     Finds the first ancestor of the given element which has the given
        ///     flags set.
        /// </summary>
        internal DependencyObject FindFirstAncestorWithFlagsAnd(VisualFlags flag)
        {
            Visual3D current = this;
 
            do
            {
                if (current.CheckFlagsAnd(flag))
                {
                    // The other Visual crossed through this Visual's parent chain. Hence this is our
                    // common ancestor.
                    return current;
                }
 
                if (current._3DParent == null)
                {
                    return VisualTreeUtils.FindFirstAncestorWithFlagsAnd(InternalVisualParent, flag);
                }
 
                current = current._3DParent;
            }
            while (current != null);
 
            return null;
        }
 
        /// <summary>
        /// Finds the common ancestor of two Visuals.
        /// </summary>
        /// <returns>Returns the common ancestor if the Visuals have one or otherwise null.</returns>
        /// <exception cref="ArgumentNullException">If the argument is null.</exception>
        public DependencyObject FindCommonVisualAncestor(DependencyObject otherVisual)
        {
            VerifyAPIReadOnly(otherVisual);
 
            ArgumentNullException.ThrowIfNull(otherVisual);
 
            // Since we can't rely on code running in the CLR, we need to first make sure
            // that the FindCommonAncestor flag is not set. It is enought to ensure this
            // on one path to the root Visual.
 
 
            // Later, when we get from the CLR the "RunForSure" section support, we can replace
            // this algorithm with one that is linear in the distance of the two visuals to
            // their common ancestor.
 
            SetFlagsToRoot(false, VisualFlags.FindCommonAncestor);
 
            // Walk up the other visual's parent chain and set the FindCommonAncestor flag.
            VisualTreeUtils.SetFlagsToRoot(otherVisual, true, VisualFlags.FindCommonAncestor);
 
            // Now see if the other Visual's parent chain crosses our parent chain.
            return FindFirstAncestorWithFlagsAnd(VisualFlags.FindCommonAncestor);
        }
 
        /// <summary>
        /// Override this function in derived classes to release unmanaged resources during Dispose
        /// and during removal of a subtree.
        /// </summary>
        internal void FreeDUCEResources(DUCE.Channel channel)
        {
            Transform3D transform = Transform;
            if (!CheckFlagsAnd(channel, VisualProxyFlags.IsTransformDirty))
            {
                if (InternalIsVisible)
                {
                    if (transform != null)
                    {
                        ((DUCE.IResource)transform).ReleaseOnChannel(channel);
                    }
                }
                else
                {
                    ((DUCE.IResource)_zeroScale).ReleaseOnChannel(channel);
                }
            }
 
            Model3D model = _visual3DModel;
            if ((model != null) && (!CheckFlagsAnd(channel, VisualProxyFlags.IsContentDirty)))
            {
                ((DUCE.IResource)model).ReleaseOnChannel(channel);
            }
 
            Debug.Assert(IsOnChannel(channel));
            Debug.Assert(!CheckFlagsAnd(channel, VisualProxyFlags.IsContentNodeConnected));
 
            _proxy.ReleaseOnChannel(channel);
        }
 
        void DUCE.IResource.ReleaseOnChannel(DUCE.Channel channel)
        {
            ReleaseOnChannelCore(channel);
        }
 
        internal void ReleaseOnChannelCore(DUCE.Channel channel)
        {
            if (!IsOnChannel(channel))
            {
                return;
            }
 
            // at this point the tree is not connected any more.
            SetFlags(channel, false, VisualProxyFlags.IsConnectedToParent);
 
            FreeDUCEResources(channel);
 
            for (int i = 0; i < Visual3DChildrenCount; i++)
            {
                Visual3D child = GetVisual3DChild(i);
                ((DUCE.IResource)child).ReleaseOnChannel(channel);
            }
        }
 
        /// <summary>
        /// Disconnects a resource attached to this visual.
        /// </summary>
        internal void DisconnectAttachedResource(
            VisualProxyFlags correspondingFlag,
            DUCE.IResource attachedResource)
        {
            //
            // Iterate over the channels this visual is being marshaled to
            //
 
            for (int i = 0; i < _proxy.Count; i++)
            {
                VisualProxyFlags flags = _proxy.GetFlags(i);
 
                if ((flags & correspondingFlag) == 0)
                {
                    DUCE.Channel channel = _proxy.GetChannel(i);
 
                    //
                    // Set the flag so that during render we send
                    // update to the compositor.
                    //
                    SetFlags(channel, true, correspondingFlag);
 
                    if (correspondingFlag == VisualProxyFlags.IsContentDirty)
                    {
                        _proxy.SetFlags(i, false, VisualProxyFlags.IsContentConnected);
                    }
 
                    attachedResource.ReleaseOnChannel(channel);
                }
            }
        }
 
        // Normally the inheritence context is the same as our parent.  When it differs,
        // we store the value in the _inheritanceContext UncommonField.  See comments
        // on the _inheritanceContext and UseParentAsContext members for more info.
        internal override DependencyObject InheritanceContext
        {
            get
            {
                DependencyObject value = _inheritanceContext.GetValue(this);
 
                if (value == UseParentAsContext)
                {
                    return InternalVisualParent;
                }
 
                return value;
            }
        }
 
        internal override void AddInheritanceContext(DependencyObject context, DependencyProperty property)
        {
            base.AddInheritanceContext(context, property);
 
            // Call local helper
            AddOrRemoveInheritanceContext(context);
        }
 
        internal override void RemoveInheritanceContext(DependencyObject context, DependencyProperty property)
        {
            base.RemoveInheritanceContext(context, property);
 
            // Call local helper
            AddOrRemoveInheritanceContext(null);
        }
 
        // Local helping for adding or removing an inheritance context
        private void AddOrRemoveInheritanceContext(DependencyObject newInheritanceContext)
        {
            // If we are using our parent as our inheritance context the InheritanceContext
            // property will already be returning the new context, but we still need to treat
            // this as a change so we notify our dependants.
            bool contextChanged =
                InheritanceContext != newInheritanceContext ||
                (_inheritanceContext.GetValue(this) == UseParentAsContext &&
                    newInheritanceContext == InternalVisualParent);
 
            if (contextChanged)
            {
                // Pick up the new context
                SetInheritanceContext(newInheritanceContext);
                OnInheritanceContextChanged(EventArgs.Empty);
            }
        }
 
        internal override bool HasMultipleInheritanceContexts
        {
            get { return base.HasMultipleInheritanceContexts; }
        }
 
        internal override void OnInheritanceContextChangedCore(EventArgs args) // ancestor changed
        {
            base.OnInheritanceContextChangedCore(args);
 
            for (int i = 0; i < Visual3DChildrenCount; i++)
            {
                DependencyObject child = GetVisual3DChild(i);
 
                Debug.Assert(child.InheritanceContext == this,
                    "How did a child get inserted without propagating our inheritance context?");
 
                child.OnInheritanceContextChanged(args);
            }
        }
 
        #endregion Internal Methods
 
        // --------------------------------------------------------------------
        //
        //   Visual-to-Visual Transforms
        //
        // --------------------------------------------------------------------
 
        #region Visual-to-Visual Transforms
 
        /// <summary>
        /// Returns a transform that can be used to transform coordinates from this
        /// node to the specified ancestor, or null if the transformation cannot be created.
        /// 2D is allowed to be between the 3D nodes.
        /// </summary>
        /// <exception cref="ArgumentNullException">
        /// If ancestor is null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// If the ancestor Visual3D is not a ancestor of Visual3D.
        /// </exception>
        /// <exception cref="InvalidOperationException">If the Visual3Ds are not connected.</exception>
        public GeneralTransform3D TransformToAncestor(Visual3D ancestor)
        {
            ArgumentNullException.ThrowIfNull(ancestor);
 
            VerifyAPIReadOnly(ancestor);
 
            return InternalTransformToAncestor(ancestor, false);
        }
 
 
        /// <summary>
        /// Returns a transform that can be used to transform coordinates from this
        /// node to the specified descendant, or null if the transform from descendant to "this"
        /// is non-invertible.  This is the case when 2D is between the nodes.
        ///
        /// </summary>
        /// <exception cref="ArgumentException">
        /// If the reference Visual3D is not a ancestor of the descendant Visual3D.
        /// </exception>
        /// <exception cref="ArgumentNullException">If the descendant argument is null.</exception>
        /// <exception cref="InvalidOperationException">If the Visual3Ds are not connected.</exception>
        public GeneralTransform3D TransformToDescendant(Visual3D descendant)
        {
            ArgumentNullException.ThrowIfNull(descendant);
 
            VerifyAPIReadOnly(descendant);
 
            return descendant.InternalTransformToAncestor(this, true);
        }
 
        /// <summary>
        /// Returns the transform or the inverse transform between this visual and the specified ancestor.
        /// If inverse is requested but does not exist (if the transform is not invertible), null is returned.
        /// </summary>
        /// <param name="ancestor">Ancestor visual.</param>
        /// <param name="inverse">Returns inverse if this argument is true.</param>
        private GeneralTransform3D InternalTransformToAncestor(Visual3D ancestor, bool inverse)
        {
            Debug.Assert(ancestor != null);
 
            // used to track if all the collected transforms on the way to the ancestor were valid
            bool success = true;
 
            DependencyObject g = this;
            Visual3D lastVisual3D = null;
 
            Matrix3D m = Matrix3D.Identity;
            GeneralTransform3DGroup group = null;
 
            // This while loop will walk up the visual tree until we encounter the ancestor.
            // As it does so, it will accumulate the descendent->ancestor transform.
            // In most cases, this is simply a matrix, though if we encounter a 2D node we need to
            // transform from 3D out in to 2D and then back in to 3D and continue the parent walk.
            // We will accumulate the current transform in a matrix until we encounter a 2D parent,
            // at which point we will add the matrix's current value and the transform from 3D to 2D to 3D
            // to the GeneralTransform3DGroup and continue to accumulate further transforms in the matrix again.
            // At the end of this loop, we will have 0 or more transforms in the GeneralTransform3DGroup
            // and the matrix which, if not identity, should be appended to the GeneralTransform3DGroup.
            // If, as is commonly the case, this loop terminates without encountering a 2D parent
            // we will simply use the Matrix3D.
 
            while ((VisualTreeHelper.GetParent(g) != null) && (g != ancestor))
            {
                Visual3D gAsVisual3D = g as Visual3D;
                if (gAsVisual3D != null)
                {
                    Transform3D transform = gAsVisual3D.Transform;
                    if (transform != null)
                    {
                        transform.Append(ref m);
                    }
 
                    lastVisual3D = gAsVisual3D;
                    g = VisualTreeHelper.GetParent(gAsVisual3D);
                }
                else
                {
                    if (group == null)
                    {
                        group = new GeneralTransform3DGroup();
                    }
 
                    group.Children.Add(new MatrixTransform3D(m));
                    m = Matrix3D.Identity;
 
                    // construct the 3D to 2D to 3D transform
                    Visual gAsVisual = g as Visual;
                    GeneralTransform3DTo2D transform3DTo2D = lastVisual3D.TransformToAncestor(gAsVisual);
 
                    // now find the 3D parent of the 2D object
                    Visual3D containing3DParent = VisualTreeHelper.GetContainingVisual3D(gAsVisual);
 
                    // if containing3DParent is null, then the ancestor parameter is not really an ancestor
                    // break out of the loop to allow it to fail
                    if (containing3DParent == null)
                    {
                        break;
                    }
 
                    GeneralTransform2DTo3D transform2DTo3D = gAsVisual.TransformToAncestor(containing3DParent);
 
                    // if either transform ends up being null then we don't have a transform
                    if (transform3DTo2D == null || transform2DTo3D == null)
                    {
                        // we don't want to break here because although we own't be able to create a valid transformation
                        // we also want to throw an exception if the ancestor passed in is not a valid ancestor.  We then
                        // continue the tree walk to make sure.
                        success = false;
                    }
                    else
                    {
                        group.Children.Add(new GeneralTransform3DTo2DTo3D(transform3DTo2D, transform2DTo3D));
                    }
 
                    // the last visual3D found is where we continue the search
                    g = containing3DParent;
                }
            }
 
            if (g != ancestor)
            {
                throw new System.InvalidOperationException(inverse ? SR.Visual_NotADescendant : SR.Visual_NotAnAncestor);
            }
 
            // construct the generaltransform3d to return and invert it if necessary
            GeneralTransform3D finalTransform = null;
 
            // if we successfully found a transform then we can create it here, otherwise finalTransform stays null
            if (success)
            {
                if (group != null)
                {
                    finalTransform = group;
                }
                else
                {
                    finalTransform = new MatrixTransform3D(m);
                }
 
                if (inverse)
                {
                    finalTransform = finalTransform.Inverse;
                }
            }
 
            if (finalTransform != null)
            {
                finalTransform.Freeze();
            }
 
            return finalTransform;
        }
 
        /// <summary>
        /// Returns a transform that can be used to transform coordinate from this
        /// node to the specified ancestor, or null if the transform does not exist.
        /// This transform will take a 3D point, and then project it in to 2D space.
        /// The resulting point is the transformed 3D point in the coordinate space
        /// of the given ancestor.
        ///
        /// </summary>
        /// <exception cref="ArgumentNullException">
        /// If ancestor is null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// If the ancestor Visual is not an ancestor of this.
        /// </exception>
        /// <exception cref="InvalidOperationException">If the Visual3D and Visual are not connected.</exception>
        public GeneralTransform3DTo2D TransformToAncestor(Visual ancestor)
        {
            ArgumentNullException.ThrowIfNull(ancestor);
 
            VerifyAPIReadOnly(ancestor);
 
            return InternalTransformToAncestor(ancestor);
        }
 
        /// <summary>
        /// Provides the transform between this visual3D and the specified ancestor, or null
        /// if the transform does not exist.
        ///
        /// </summary>
        /// <param name="ancestor">Ancestor visual.</param>
        /// <returns>The transform from 3D to 2D</returns>
        internal GeneralTransform3DTo2D InternalTransformToAncestor(Visual ancestor)
        {
            Debug.Assert(ancestor != null);
 
            // get the transform out of 3D and in to 2D
            Viewport3DVisual containingViewport;
            Matrix3D projectionTransform;
 
            if (!M3DUtil.TryTransformToViewport3DVisual(this, out containingViewport, out projectionTransform))
            {
                return null;
            }
 
            // get the transform from the Viewport3DVisual to the ancestor
            GeneralTransform transformIn2D = containingViewport.TransformToAncestor(ancestor);
 
            // package the two up in the transformTo2D
            GeneralTransform3DTo2D result = new GeneralTransform3DTo2D(projectionTransform, transformIn2D);
            result.Freeze();
 
            return result;
        }
 
        #endregion Visual-to-Visual Transforms
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
 
        #region Internal Properties
 
        internal DependencyObject InternalVisualParent
        {
            get
            {
                if (_3DParent != null)
                {
                    Debug.Assert(_2DParent.GetValue(this) == null,
                        "Only one parent pointer should be set at a time.");
 
                    return _3DParent;
                }
 
                DependencyObject parent2D = _2DParent.GetValue(this);
 
                Debug.Assert(parent2D == null || parent2D is Viewport3DVisual,
                    "The only acceptable 2D parent for a Visual3D is a Viewport3DVisual.");
 
                return parent2D;
            }
        }
 
        internal int ParentIndex
        {
            get { return _parentIndex; }
            set { _parentIndex = value; }
        }
 
        // Are we in the process of iterating the visual children.
        // This flag is set during a descendents walk, for property invalidation.
        internal bool IsVisualChildrenIterationInProgress
        {
            get { return CheckFlagsAnd(VisualFlags.IsVisualChildrenIterationInProgress); }
 
            set { SetFlags(value, VisualFlags.IsVisualChildrenIterationInProgress); }
        }
 
        #endregion Internal Properties
 
        // --------------------------------------------------------------------
        //
        //   Visual flags manipulation
        //
        // --------------------------------------------------------------------
 
        #region Visual flags manipulation
 
        /// <summary>
        /// SetFlagsOnAllChannels is used to set or unset one
        /// or multiple flags on all channels this visual is
        /// marshaled to.
        /// </summary>
        internal void SetFlagsOnAllChannels(
            bool value,
            VisualProxyFlags flagsToChange)
        {
            _proxy.SetFlagsOnAllChannels(
                value,
                flagsToChange);
        }
 
        /// <summary>
        /// SetFlags is used to set or unset one or multiple flags on a given channel.
        /// </summary>
        internal void SetFlags(
            DUCE.Channel channel,
            bool value,
            VisualProxyFlags flagsToChange)
        {
            _proxy.SetFlags(
                channel,
                value,
                flagsToChange);
        }
 
        /// <summary>
        /// SetFlags is used to set or unset one or multiple node flags on the node.
        /// </summary>
        internal void SetFlags(bool value, VisualFlags Flags)
        {
            _flags = value ? (_flags | Flags) : (_flags & (~Flags));
        }
 
        /// <summary>
        /// CheckFlagsOnAllChannels returns true if all flags in
        /// the bitmask flags are set on all channels this visual is
        /// marshaled to.
        /// </summary>
        /// <remarks>
        /// If there aren't any bits set on the specified flags
        /// the method returns true.
        /// </remarks>
        internal bool CheckFlagsOnAllChannels(VisualProxyFlags flagsToCheck)
        {
            return _proxy.CheckFlagsOnAllChannels(flagsToCheck);
        }
 
        /// <summary>
        /// CheckFlagsAnd returns true if all flags in the bitmask flags
        /// are set on a given channel.
        /// </summary>
        /// <remarks>
        /// If there aren't any bits set on the specified flags
        /// the method returns true.
        /// </remarks>
        internal bool CheckFlagsAnd(
            DUCE.Channel channel,
            VisualProxyFlags flagsToCheck)
        {
            return (_proxy.GetFlags(channel) & flagsToCheck) == flagsToCheck;
        }
 
        /// <summary>
        /// CheckFlagsAnd returns true if all flags in the bitmask flags are set on the node.
        /// </summary>
        /// <remarks>If there aren't any bits set on the specified flags the method
        /// returns true</remarks>
        internal bool CheckFlagsAnd(VisualFlags flags)
        {
            return (_flags & flags) == flags;
        }
 
        /// <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 virtual DependencyObject InternalGet2DOr3DVisualChild(int index)
        {
            // Call the right virtual method.
            return GetVisual3DChild(index);
        }
 
        /// <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 virtual int InternalVisual2DOr3DChildrenCount
        {
            get
            {
                // Call the right virtual method.
                return Visual3DChildrenCount;
            }
        }
 
        /// <summary>
        /// Checks if any of the specified flags is set on a given channel.
        /// </summary>
        /// <remarks>
        /// If there aren't any bits set on the specified flags
        /// the method returns true.
        /// </remarks>
        internal bool CheckFlagsOr(
            DUCE.Channel channel,
            VisualProxyFlags flagsToCheck)
        {
            return (_proxy.GetFlags(channel) & flagsToCheck) != VisualProxyFlags.None;
        }
 
        /// <summary>
        /// Checks if any of the specified flags is set on the node.
        /// </summary>
        /// <remarks>If there aren't any bits set on the specified flags the method
        /// returns true</remarks>
        internal bool CheckFlagsOr(VisualFlags flags)
        {
            return (flags == 0) || ((_flags & flags) > 0);
        }
 
        /// <summary>
        ///     Check all the children for a bit.
        /// </summary>
        internal static bool DoAnyChildrenHaveABitSet(Visual3D pe,
                                                      VisualFlags flag)
        {
            int count = pe.InternalVisual2DOr3DChildrenCount;
            for (int i = 0; i < count; i++)
            {
                DependencyObject child = pe.InternalGet2DOr3DVisualChild(i);
 
                Visual v = null;
                Visual3D v3D = null;
                VisualTreeUtils.AsNonNullVisual(child, out v, out v3D);
 
                if (v != null && v.CheckFlagsAnd(flag))
                {
                    return true;
                }
                else if (v3D != null && v3D.CheckFlagsAnd(flag))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        /// <summary>
        /// Propagates the flags up to the root.
        /// </summary>
        /// <remarks>
        /// The walk stops on a node with all of the required flags set.
        /// </remarks>
        internal static void PropagateFlags(
            Visual3D e,
            VisualFlags flags,
            VisualProxyFlags proxyFlags)
        {
            while ((e != null) &&
                   (!e.CheckFlagsAnd(flags) || !e.CheckFlagsOnAllChannels(proxyFlags)))
            {
                // These asserts are mostly for documentation when diffing the 2D/3D
                // implementations.
                Debug.Assert(!e.CheckFlagsOr(VisualFlags.ShouldPostRender),
                    "Visual3Ds should never be the root of a tree.");
                 Debug.Assert(!e.CheckFlagsOr(VisualFlags.NodeIsCyclicBrushRoot),
                    "Visual3Ds should never be the root of an ICyclicBrush.");
 
                e.SetFlags(true, flags);
                e.SetFlagsOnAllChannels(true, proxyFlags);
 
                // If our 3D parent is null call back into VisualTreeUtils to potentially
                // continue the walk in 2D.
                if (e._3DParent == null)
                {
                    Viewport3DVisual viewport = e.InternalVisualParent as Viewport3DVisual;
 
                    Debug.Assert((viewport == null) == (e.InternalVisualParent == null),
                        "Viewport3DVisual is the only supported 2D parent of a 3D visual.");
 
                    if(viewport != null)
                    {
                        // We must notify the 2D visual that its contents have changed.
                        // This will cause the 2D visual to set it's content dirty flag
                        // and continue the propagation of IsDirtyForRender/Precompute.
                        viewport.Visual3DTreeChanged();
 
                        // continue propagating flags up the 2D world
                        Visual.PropagateFlags(viewport, flags, proxyFlags);
                    }
 
                    // Stop propagating.  We are at the root of the 3D subtree.
                    return;
                }
 
                e = e._3DParent;
            }
        }
 
        #endregion Visual flags manipulation
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        private void SetInheritanceContext(DependencyObject newInheritanceContext)
        {
            if (newInheritanceContext == InternalVisualParent)
            {
                _inheritanceContext.ClearValue(this);
 
                Debug.Assert(_inheritanceContext.GetValue(this) == UseParentAsContext,
                    "Clearing the _inheritanceContext uncommon field should put us in the UseParentAsContext state.");
            }
            else
            {
                _inheritanceContext.SetValue(this, newInheritanceContext);
            }
        }
 
        #endregion Private Methods
 
        //------------------------------------------------------
        //
        //  Internal Fields
        //
        //------------------------------------------------------
 
        #region Internal Fields
 
        internal VisualProxy _proxy;
 
        #endregion Internal Fields
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        // If the parent of the Visual3D is another Visual3D we store the parent in the _3DParent field.
        // If the parent is a 2D node (as is the case when this is the root of a Viewport3DVisual) we
        // store the parent in an UncommonField.  Both fields must be considered when determining
        // the parent of this node.
        private static readonly UncommonField<Visual> _2DParent =
            new UncommonField<Visual>(/* defaultValue = */ null);
 
        // Sentinel value we use to differentiate between a null inheritance context stored in the
        // _inheritanceContext uncommon field and "empty", meaning use the parent as context.
        private static readonly DependencyObject UseParentAsContext = new DependencyObject();
 
        // Normally the inheritance context is the same as the parent, except when we are parent to
        // a Viewport3D visual, in which case we use this uncommon field to store are alternate context.
        private static readonly UncommonField<DependencyObject> _inheritanceContext =
            new UncommonField<DependencyObject>(/* defaultValue = */ UseParentAsContext);
 
        private static readonly UncommonField<Visual.AncestorChangedEventHandler> AncestorChangedEventField
            = new UncommonField<Visual.AncestorChangedEventHandler>();
 
        private Visual3D _3DParent;
        private int _parentIndex = -1;
 
        private VisualFlags _flags;
 
        // Untransformed *cached* content bounds. Do not access it directly -- instead
        // use GetContentBounds()
        private Rect3D _bboxContent;
 
        // Untransformed *cached* subgraph bounds. Do not access it directly -- instead
        // use the BBoxSubgraph property.
        private Rect3D _bboxSubgraph = Rect3D.Empty;
 
        private bool _internalIsVisible;
 
        private static readonly ScaleTransform3D _zeroScale = new ScaleTransform3D(0, 0, 0);
 
        private Model3D _visual3DModel;
 
        #endregion Private Fields
 
        //------------------------------------------------------
        //
        //  Protected Fields
        //
        //------------------------------------------------------
 
        #region Protected Fields
 
        #endregion Protected Fields
    }
}