File: System\Windows\Media3D\Viewport3DVisual.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 System.ComponentModel;
using System.Windows.Diagnostics;
using System.Windows.Media.Composition;
using System.Windows.Markup;
using System.Windows.Media.Effects;
 
namespace System.Windows.Media.Media3D
{
    /// <summary>
    ///     The Viewport3DVisual provides the Camera and viewport Rect
    ///     required to project the Visual3Ds into 2D.  The Viewport3DVisual
    ///     is the bridge between 2D visuals and 3D.
    /// </summary>
    [ContentProperty("Children")]
    public sealed class Viewport3DVisual : Visual, DUCE.IResource, IVisual3DContainer
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        /// <summary>
        ///     Default constructor
        /// </summary>
        public Viewport3DVisual() : base(DUCE.ResourceType.TYPE_VIEWPORT3DVISUAL)
        {
            _children = new Visual3DCollection(this);
        }
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        #region Public Methods
 
        // ------------------------------------------------------------------------------------------
        // Publicly re-exposed VisualTreeHelper interfaces.
        //
        //     Note that we do not want to expose the Children property on Viewport3DVisual
        //     since the Viewport3DVisual provides its own set of Visual3D children.
        // ------------------------------------------------------------------------------------------
 
        /// <summary>
        /// Re-exposes the Visual base class's corresponding VisualTreeHelper implementation as public method.
        /// </summary>
        public DependencyObject Parent
        {
            get { return base.VisualParent; }
        }
 
        /// <summary>
        /// Re-exposes the Visual base class's corresponding VisualTreeHelper implementation as public method.
        /// </summary>
        public Geometry Clip
        {
            get { return base.VisualClip; }
            set { base.VisualClip = value; }
        }
 
        /// <summary>
        /// Re-exposes the Visual base class's corresponding VisualTreeHelper implementation as public method.
        /// </summary>
        public double Opacity
        {
            get { return base.VisualOpacity; }
            set { base.VisualOpacity = value; }
        }
 
        /// <summary>
        /// Re-exposes the Visual base class's corresponding VisualTreeHelper implementation as public method.
        /// </summary>
        public Brush OpacityMask
        {
            get { return base.VisualOpacityMask;  }
            set { base.VisualOpacityMask = value; }
        }
 
        /// <summary>
        /// Re-exposes the Visual base class's corresponding VisualTreeHelper implementation as public method.
        /// </summary>
        [Obsolete(MS.Internal.Media.VisualTreeUtils.BitmapEffectObsoleteMessage)]
        public BitmapEffect BitmapEffect
        {
            get { return base.VisualBitmapEffect; }
            set { base.VisualBitmapEffect = value; }
        }
 
 
        /// <summary>
        /// Re-exposes the Visual base class's corresponding VisualTreeHelper implementation as public method.
        /// </summary>
        [Obsolete(MS.Internal.Media.VisualTreeUtils.BitmapEffectObsoleteMessage)]
        public BitmapEffectInput BitmapEffectInput
        {
            get { return base.VisualBitmapEffectInput; }
            set { base.VisualBitmapEffectInput = value; }
        }
 
        /// <summary>
        /// Re-exposes the Visual base class's corresponding VisualTreeHelper implementation as public method.
        /// </summary>
        new public HitTestResult HitTest(Point point)
        {
            return base.HitTest(point);
        }
 
        /// <summary>
        /// Re-exposes the Visual base class's corresponding VisualTreeHelper implementation as public method.
        /// </summary>
        new public void HitTest(HitTestFilterCallback filterCallback, HitTestResultCallback resultCallback, HitTestParameters hitTestParameters)
        {
            base.HitTest(filterCallback, resultCallback, hitTestParameters);
        }
 
        /// <summary>
        /// VisualContentBounds returns the bounding box for the contents of this Visual.
        /// </summary>
        public Rect ContentBounds
        {
            get
            {
                return base.VisualContentBounds;
            }
        }
 
        /// <summary>
        /// Gets or sets the Transform property.
        /// </summary>
        public Transform Transform
        {
            get
            {
                return base.VisualTransform;
            }
            set
            {
                base.VisualTransform = value;
            }
        }
 
        /// <summary>
        /// Gets or sets the Offset property.
        /// </summary>
        public Vector Offset
        {
            get
            {
                return base.VisualOffset;
            }
            set
            {
                base.VisualOffset = value;
            }
        }
 
        /// <summary>
        /// DescendantBounds 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>
        public Rect DescendantBounds
        {
            get
            {
                return base.VisualDescendantBounds;
            }
        }
 
        #endregion Public Methods
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        #region Public Properties
 
        /// <summary>
        ///    DependencyProperty which backs the ModelVisual3D.Camera property.
        /// </summary>
        public static readonly DependencyProperty CameraProperty =
            DependencyProperty.Register(
                    "Camera",
                    /* propertyType = */ typeof(Camera),
                    /* ownerType = */ typeof(Viewport3DVisual),
                    new PropertyMetadata(
                        FreezableOperations.GetAsFrozen(new PerspectiveCamera()),
                        CameraPropertyChanged),
                    (ValidateValueCallback) delegate { return MediaContext.CurrentMediaContext.WriteAccessEnabled; });
 
        private static void CameraPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Viewport3DVisual owner = ((Viewport3DVisual) d);
 
            if (!e.IsASubPropertyChange)
            {
                if (e.OldValue != null)
                {
                    owner.DisconnectAttachedResource(
                        VisualProxyFlags.Viewport3DVisual_IsCameraDirty,
                        ((DUCE.IResource) e.OldValue));
                }
 
                owner.SetFlagsOnAllChannels(true, VisualProxyFlags.Viewport3DVisual_IsCameraDirty | VisualProxyFlags.IsContentDirty);
            }
 
            owner.ContentsChanged(/* sender = */ owner, EventArgs.Empty);
        }
 
        /// <summary>
        ///     Camera for this Visual3D.
        /// </summary>
        public Camera Camera
        {
            get
            {
                return (Camera) GetValue(CameraProperty);
            }
 
            set
            {
                SetValue(CameraProperty, value);
            }
        }
 
        /// <summary>
        ///    DependencyProperty which backs the ModelVisual3D.Viewport property.
        /// </summary>
        public static readonly DependencyProperty ViewportProperty =
            DependencyProperty.Register(
                    "Viewport",
                    /* propertyType = */ typeof(Rect),
                    /* ownerType = */ typeof(Viewport3DVisual),
                    new PropertyMetadata(Rect.Empty, ViewportPropertyChanged),
                    (ValidateValueCallback) delegate { return MediaContext.CurrentMediaContext.WriteAccessEnabled; });
 
        private static void ViewportPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Viewport3DVisual owner = ((Viewport3DVisual) d);
 
            Debug.Assert(!e.IsASubPropertyChange,
                "How are we receiving sub property changes from a struct?");
 
            owner.SetFlagsOnAllChannels(true, VisualProxyFlags.Viewport3DVisual_IsViewportDirty | VisualProxyFlags.IsContentDirty);
            owner.ContentsChanged(/* sender = */ owner, EventArgs.Empty);
        }
 
        /// <summary>
        ///     Viewport for this Visual3D.
        /// </summary>
        public Rect Viewport
        {
            get
            {
                return (Rect) GetValue(ViewportProperty);
            }
 
            set
            {
                SetValue(ViewportProperty, value);
            }
        }
 
        /// <summary>
        ///     The 3D children to be projected by this Viewport3DVisual.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public Visual3DCollection Children
        {
            get
            {
                return _children;
            }
        }
 
        #endregion Public Properties
 
        //------------------------------------------------------
        //
        //  Public Events
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        // 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); }
 
        // NOTE:  The code here is highly similar to AddChildCore in ModelVisual3D,
        //        but slightly different because the parent is 2D here.
        void IVisual3DContainer.AddChild(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
            if (_inheritanceContextForChildren != null)
            {
                _inheritanceContextForChildren.ProvideSelfAsInheritanceContext(child, null);
            }
 
            SetFlagsOnAllChannels(true, VisualProxyFlags.IsContentDirty);
 
            // The child already might be dirty. Hence we need to propagate dirty information
            // from the parent and from the child.
            Visual.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);
        }
 
        // NOTE:  The code here is highly similar to RemoveChildCore in ModelVisual3D,
        //        but slightly different because the parent is 2D here.
        void IVisual3DContainer.RemoveChild(Visual3D child)
        {
            int index = child.ParentIndex;
 
            // 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 == this);
 
            VisualDiagnostics.OnVisualChildChanged(this, child, false);
 
            child.SetParent(/* newParent = */ (Visual) null);  // CS0121: Call is ambigious without casting null to Visual.
 
            // remove the inheritance context
            if (_inheritanceContextForChildren != null)
            {
                _inheritanceContextForChildren.RemoveSelfAsInheritanceContext(child, null);
            }
 
            //
            // Remove the child on all channels this visual is marshalled to.
            //
 
            for (int i = 0, limit = _proxy3D.Count; i < limit; i++)
            {
                DUCE.Channel channel = _proxy3D.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);
                }
            }
 
            SetFlagsOnAllChannels(true, VisualProxyFlags.IsContentDirty);
 
            //
            // Force a full precompute and render pass for this visual.
            //
 
            Visual.PropagateFlags(
                this,
                VisualFlags.IsSubtreeDirtyForPrecompute,
                VisualProxyFlags.IsSubtreeDirtyForRender);
 
            // We do not currently support layout in 3D
            // UIElement.PropagateSuspendLayout(child);
 
            child.FireOnVisualParentChanged(this);
 
            OnVisualChildrenChanged(/* visualAdded = */ null , child);
        }
 
        /// <summary>
        ///     Gets the number of Visual3D children that the IVisual3DContainer
        ///     contains.
        /// </summary>
        int IVisual3DContainer.GetChildrenCount()
        {
            return InternalVisual2DOr3DChildrenCount;
        }
 
        /// <summary>
        ///     Gets the index children of the IVisual3DContainer
        /// </summary>
        Visual3D IVisual3DContainer.GetChild(int index)
        {
            return (Visual3D)InternalGet2DOr3DVisualChild(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 override int InternalVisual2DOr3DChildrenCount
        {
            get
            {
                return Children.Count;
            }
        }
 
        /// <summary>
        /// Used only by VisualTreeHelper.
        ///
        /// 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).
        /// </summary>
        internal override DependencyObject InternalGet2DOr3DVisualChild(int index)
        {
            return Children[index];
        }
 
        internal override HitTestResultBehavior HitTestPointInternal(
            HitTestFilterCallback filterCallback,
            HitTestResultCallback resultCallback,
            PointHitTestParameters hitTestParameters)
        {
            if (_children.Count != 0)
            {
                double distanceAdjustment;
 
                RayHitTestParameters rayParams =
                    Camera.RayFromViewportPoint(
                        hitTestParameters.HitPoint,
                        Viewport.Size,
                        BBoxSubgraph,
                        out distanceAdjustment);
 
                HitTestResultBehavior result = Visual3D.HitTestChildren(filterCallback, rayParams, this);
 
                return rayParams.RaiseCallback(resultCallback, filterCallback, result, distanceAdjustment);
            }
 
            return HitTestResultBehavior.Continue;
        }
 
        /// <summary>
        ///     Viewport3DVisual does not yet support Geometry hit testing.
        /// </summary>
        protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters)
        {
            throw new NotSupportedException(SR.Format(SR.HitTest_Invalid, typeof(GeometryHitTestParameters).Name, this.GetType().Name));
        }
 
        internal Point WorldToViewport(Point4D point)
        {
            double aspectRatio = M3DUtil.GetAspectRatio(Viewport.Size);
            Camera camera = Camera;
 
            if (camera != null)
            {
                Matrix3D viewProjMatrix = camera.GetViewMatrix() * camera.GetProjectionMatrix(aspectRatio);
                point *= viewProjMatrix;
 
                Point point2D = new Point(point.X/point.W, point.Y/point.W);
                point2D *= M3DUtil.GetHomogeneousToViewportTransform(Viewport);
 
                return point2D;
            }
            else
            {
                return new Point(0,0);
            }
        }
 
        /// <summary>
        /// Derived classes return the hit-test bounding box from the
        /// GetHitTestBounds virtual. Visual uses the bounds to optimize
        /// hit-testing.
        /// </summary>
        internal override Rect GetHitTestBounds()
        {
            return CalculateSubgraphBoundsInnerSpace();
        }
 
        internal override Rect CalculateSubgraphBoundsInnerSpace(bool renderBounds)
        {
            Camera camera = Camera;
 
            if (camera == null)
            {
                return Rect.Empty;
            }
 
            //
            // Cache the 3D bounding box for future use. Here we are relying on
            // the fact that this method is called by PrecomputeContent(),
            // which is called prior to all usages of _bboxChildrenSubgraph3D.
            //
            _bboxChildrenSubgraph3D = ComputeSubgraphBounds3D();
 
            if (_bboxChildrenSubgraph3D.IsEmpty)
            {
                // Attempting to project empty bounds will result in NaNs which
                // which will ruin descendant bounds for the 2D tree.  We handle
                // this explicitly and early exit with the correct answer.
 
                return Rect.Empty;
            }
 
            Rect viewport = Viewport;
 
            // Common Case: Viewport3DVisual in a collasped UIElement.
            if (viewport.IsEmpty)
            {
                // Creating a 3D homogenous space to 2D viewport space transform
                // with an empty rectangle will result in NaNs which ruin the
                // descendant bounds for the 2D tree.  We handle this explicitly
                // and early exit with the correct answer.
                //
                // (Usage of Visibility="Collapsed" attribute in avalon Viewport3D element triggers assertion failed: IsWellOrdered())
 
                return Rect.Empty;
            }
 
            double aspectRatio = M3DUtil.GetAspectRatio(viewport.Size);
            Matrix3D viewProjMatrix = camera.GetViewMatrix() * camera.GetProjectionMatrix(aspectRatio);
            Rect projectedBounds2D = MILUtilities.ProjectBounds(ref viewProjMatrix, ref _bboxChildrenSubgraph3D);
            Matrix homoToLocal = M3DUtil.GetHomogeneousToViewportTransform(viewport);
            MatrixUtil.TransformRect(ref projectedBounds2D, ref homoToLocal);
 
            return projectedBounds2D;
        }
 
        //
        // NOTE: Must only be called after PrecomputeContent().
        //
        private Rect3D BBoxSubgraph
        {
            get
            {
                Debug_VerifyCachedSubgraphBounds();
 
                return _bboxChildrenSubgraph3D;
            }
        }
 
        internal Rect3D ComputeSubgraphBounds3D()
        {
            Rect3D bboxChildrenSubgraph3D = Rect3D.Empty;
 
            for (int i = 0, count = _children.InternalCount; i < count; i++)
            {
                Visual3D child = _children.InternalGetItem(i);
 
                bboxChildrenSubgraph3D.Union(child.CalculateSubgraphBoundsOuterSpace());
            }
 
            return bboxChildrenSubgraph3D;
        }
 
        [Conditional("DEBUG")]
        private void Debug_VerifyCachedSubgraphBounds()
        {
            Rect3D currentBounds = Rect3D.Empty;
            currentBounds = ComputeSubgraphBounds3D();
 
            Rect3D cachedBounds = _bboxChildrenSubgraph3D;
 
            // The funny boolean logic below avoids asserts when the cached
            // bounds contain NaNs.  (NaN != NaN)
            bool boundsAreEqual =
                !(cachedBounds.X < currentBounds.X || cachedBounds.X > currentBounds.X) &&
                !(cachedBounds.Y < currentBounds.Y || cachedBounds.Y > currentBounds.Y) &&
                !(cachedBounds.Z < currentBounds.Z || cachedBounds.Z > currentBounds.Z) &&
                !(cachedBounds.SizeX < currentBounds.SizeX || cachedBounds.SizeX > currentBounds.SizeX) &&
                !(cachedBounds.SizeY < currentBounds.SizeY || cachedBounds.SizeY > currentBounds.SizeY) &&
                !(cachedBounds.SizeZ < currentBounds.SizeZ || cachedBounds.SizeZ > currentBounds.SizeZ);
 
            if (!boundsAreEqual)
            {
                Debug.Fail("Cached bbox subgraph is incorrect!");
            }
        }
 
        internal override DUCE.ResourceHandle AddRefOnChannelCore(DUCE.Channel channel)
        {
            DUCE.ResourceHandle handle =
                base.AddRefOnChannelCore(channel);
 
            bool created = _proxy3D.CreateOrAddRefOnChannel(this, channel, DUCE.ResourceType.TYPE_VISUAL3D);
 
            Debug.Assert(
                _proxy.Count == _proxy3D.Count,
                "Viewport has been marshalled to a different number of channels than the 3D content.");
 
            // If we are creating the Viewport3DVisual/Visual3D for the first
            // time on this channel we need to connect the 3D root.
 
            if (created)
            {
                DUCE.Viewport3DVisualNode.Set3DChild(
                    handle,
                    _proxy3D.GetHandle(channel),
                    channel);
            }
 
            return handle;
        }
 
        internal override void ReleaseOnChannelCore(DUCE.Channel channel)
        {
            Debug.Assert(
                _proxy.Count == _proxy3D.Count,
                "Viewport has been marshalled to a different number of channels than the 3D content.");
 
            base.ReleaseOnChannelCore(channel);
 
            _proxy3D.ReleaseOnChannel(channel);
        }
 
        int DUCE.IResource.GetChannelCount()
        {
            return _proxy.Count;
        }
 
        DUCE.Channel DUCE.IResource.GetChannel(int index)
        {
            return _proxy.GetChannel(index);
        }
 
        /// <summary>
        /// Precompute pass.
        /// </summary>
        internal override void PrecomputeContent()
        {
            base.PrecomputeContent();
 
            if (_children != null)
            {
                for (int i = 0, count = _children.InternalCount; i < count; i++)
                {
                    Visual3D child = _children.InternalGetItem(i);
 
                    if (child != null)
                    {
                        // This merely tells the Visual3Ds to update their bounds
                        // caches if necessary. Later on we'll get the cached bounds
                        // from calling Visual3D.CalculateSubgraphBounds
                        Rect3D bboxSubgraphChildIgnored;
                        child.PrecomputeRecursive(out bboxSubgraphChildIgnored);
                    }
                }
            }
        }
 
        internal override void RenderContent(RenderContext ctx, bool isOnChannel)
        {
            DUCE.Channel channel = ctx.Channel;
 
            //
            // At this point, the visual has to be marshalled. Force
            // marshalling of the camera and viewport in case we have
            // just created a new visual resource.
            //
 
            Debug.Assert(IsOnChannel(channel));
            VisualProxyFlags flags = _proxy.GetFlags(channel);
 
 
            //
            // Make sure the camera resource is being marshalled properly.
            //
 
            if ((flags & VisualProxyFlags.Viewport3DVisual_IsCameraDirty) != 0)
            {
                Camera camera = Camera;
                if (camera != null)
                {
                    DUCE.Viewport3DVisualNode.SetCamera(
                        ((DUCE.IResource)this).GetHandle(channel),
                        ((DUCE.IResource)camera).AddRefOnChannel(channel),
                        channel);
                }
                else if (isOnChannel) /* camera == null */
                {
                    DUCE.Viewport3DVisualNode.SetCamera(
                        ((DUCE.IResource)this).GetHandle(channel),
                        DUCE.ResourceHandle.Null,
                        channel);
                }
                SetFlags(channel, false, VisualProxyFlags.Viewport3DVisual_IsCameraDirty);
            }
 
 
            //
            // Set the viewport if it's dirty.
            //
 
            if ((flags & VisualProxyFlags.Viewport3DVisual_IsViewportDirty) != 0)
            {
                DUCE.Viewport3DVisualNode.SetViewport(
                    ((DUCE.IResource)this).GetHandle(channel),
                    Viewport,
                    channel);
                SetFlags(channel, false, VisualProxyFlags.Viewport3DVisual_IsViewportDirty);
            }
 
 
            //we only want to recurse in the children if the visual does not have a bitmap effect
            //or we are in the BitmapVisualManager render pass
 
            // Visit children of this node -----------------------------------------------------------------------
 
            Debug.Assert(!CheckFlagsAnd(channel, VisualProxyFlags.IsContentNodeConnected),
                "Only HostVisuals are expected to have a content node.");
 
            if (_children != null)
            {
                for (uint i = 0; i < _children.InternalCount; i++)
                {
                    Visual3D child = _children.InternalGetItem((int) 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(channel))
                        {
                            if (!child.CheckFlagsAnd(channel, VisualProxyFlags.IsConnectedToParent))
                            {
                                DUCE.Visual3DNode.InsertChildAt(
                                    _proxy3D.GetHandle(channel),
                                    ((DUCE.IResource)child).GetHandle(channel),
                                    /* iPosition = */ i,
                                    channel);
 
                                child.SetFlags(channel, true, VisualProxyFlags.IsConnectedToParent);
                            }
                        }
                    }
                }
            }
        }
 
        /// <summary>
        /// Override this function in derived classes to release unmanaged resources during Dispose
        /// and during removal of a subtree.
        /// </summary>
        internal override void FreeContent(DUCE.Channel channel)
        {
            Debug.Assert(IsOnChannel(channel));
            Camera camera = Camera;
 
            if (camera != null)
            {
                if (!CheckFlagsAnd(channel, VisualProxyFlags.Viewport3DVisual_IsCameraDirty))
                {
                    ((DUCE.IResource)camera).ReleaseOnChannel(channel);
 
                    SetFlagsOnAllChannels(true, VisualProxyFlags.Viewport3DVisual_IsCameraDirty);
                }
            }
 
            if (_children != null)
            {
                for (int i = 0; i < _children.InternalCount; i++)
                {
                    Visual3D visual = _children.InternalGetItem(i);
                    ((DUCE.IResource)visual).ReleaseOnChannel(channel);
                }
            }
 
            SetFlagsOnAllChannels(true, VisualProxyFlags.IsContentDirty);
 
            base.FreeContent(channel);
        }
 
        // Notify the Viewport3DVisual that the Visual3D subtree it hosts
        // has been modified.
        internal void Visual3DTreeChanged()
        {
            // The Visual3D tree is plugged into the 2D Visual tree via the
            // same extensibility point we use for "content".
            SetFlagsOnAllChannels(true, VisualProxyFlags.IsContentDirty);
 
            ContentsChanged(/* sender = */ this, EventArgs.Empty);
        }
 
        /// <summary>
        /// Returns the handle this visual has on the given channel.
        /// Note: The 3D handle is obtained from _proxy3D.
        /// </summary>
        DUCE.ResourceHandle DUCE.IResource.Get3DHandle(DUCE.Channel channel)
        {
            return _proxy3D.GetHandle(channel);
        }
 
 
        // Because 2D Visuals and FEs do not participate in inheritance context
        // we allow this backdoor for a Viewport3D to set itself as the inheritance
        // context of the Visual3DCollection it exposes as Children.
        internal void SetInheritanceContextForChildren(DependencyObject inheritanceContextForChildren)
        {
            _inheritanceContextForChildren = inheritanceContextForChildren;
        }
 
        #endregion Internal Methods
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        /// <summary>
        /// The 3D content root.
        /// </summary>
        /// <remarks>
        /// Important! - Not readonly because CS will silently copy
        /// for self-modifying methods. (C# Spec 14.5.4)
        /// </remarks>
        private VisualProxy _proxy3D;
 
        private Rect3D _bboxChildrenSubgraph3D;
 
        private readonly Visual3DCollection _children;
 
        private DependencyObject _inheritanceContextForChildren;
 
        #endregion Private Fields
    }
}