Robin E Davies

I've been experimenting with Interactive3DDecorator (https://blogs.msdn.com/wpf3d/archive/2006/12/12/interacting-with-2d-on-3d-in-wpf.aspx). Truly stupendously amazing. Somebody needs to give that guy a medal! An amazing innovation that has the potential to seriously change the rules of the game, I think.

The basic plot summary, for those who haven't seen it yet: fully interactive XAML content rendered on 3D surfaces.

However... I've noticed that I seem to get aliased edges along the edges of some content but not others. Text is exceptionally crisp and clean; the interior of controls is good too. But I seem to get aliasing along the outside edges of controls when surfaces are rotated in 3D. The inside edge of the lines along the edge are anti-aliased, but the outside edges are aliased). If I had to guess, I would guess that the problem relates to un-anti-aliased clip regions on the outside edges of controls.

Is this a known issue. Related to my video drivers Is there a way to work around the problem. I'm guessing that I could probably make the issue go away by padding out control templates with a little bit of a transparent border.

Any information or insight into what's going on would be much appreciated.

I don't have a good site to host a bitmap from. Is there a way to upload images here



Re: Windows Presentation Foundation (WPF) Odd aliasing effects with Interactive3DDecorator

Robin E. Davies

Update: the issue seems to be related to improper alpha blending along the borders between content and areas that have no content at all. Setting the background of the window to transparent (#00FFFFFF) cures the problem. Setting the background to some other color cures the aliasing around the edges of controls, but not along the borders of the parent grid.

I can live with that. But, I'm still curious.

Is this a driver issue, or a limitation in the current implementation





Re: Windows Presentation Foundation (WPF) Odd aliasing effects with Interactive3DDecorator

Kurt Berglund - MSFT

Hi Robin,

We're glad you like the 2D on 3D code - we were very excited to release it and can't wait to see what people are able to do with it.

As for your question, I'm not sure why this aliasing is happening. Do you have a Flickr account, or anything like that where you could post images Or even if you had a simple XAML snippet where we could run it ourselves, that would probably work just as well too.





Re: Windows Presentation Foundation (WPF) Odd aliasing effects with Interactive3DDecorator

Robin E. Davies

Here are screenshots of the effect, with and without background:

http://www.flickr.com/photos/rerdavies/

The screenshots look relatively benign, but the aliased edges are very noticable when animating. A background of #00000000 cures the edge problem, but WPF 3D doesn't really handle transparent objects well, because it uses a depth buffer.

Windows XP SP2, fully serviced-packed. RTM version of WPF/.net Framework 3.0. Graphics adapter: NVidiea GeForce 6100, 512MB, Driver version: 9.1.3.1 (WHQL), 6/1/2006.

The project is a relatively straightfoward tweak of one of the samples. Window1.xaml hosts the 2D-on-3D elements, and references TestDialog.xaml (a simple 2D dialog).

meshTest:InteractiveMesh inherits directly from InteractiveVisual3D, and renders a deformed mesh on which to paint the contents of TestDialog.xaml.

If you want, drop me an email at rerdavies at rogers dot com, and I can ZIP up the entire project for you.

Code is play code. No warranties as to fitness or merchantability. Or good style.

Excerpts of code follow.

Window1.xaml

<Window x:Class="MeshDeformation.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:meshTest="clr-namespace:MeshDeformation"
  xmlns:interactive3D="clr-namespace:_3DTools;assembly=3DTools"
  
  Title="MeshDeformation" Height="700" Width="700"
  >
 <Window.Resources>
  <Grid x:Key="UserInterface">
   <meshTest:TestDialog />
  </Grid>

  <Transform3DGroup x:Key="Transform1" >
   <ScaleTransform3D ScaleX="3" ScaleY="3" ScaleZ="3" />
   <TranslateTransform3D OffsetX="-1" OffsetY="-1" />

  </Transform3DGroup>

 </Window.Resources>
 <Window.Triggers>
  <EventTrigger SourceName="closeBtn" RoutedEvent="Button.Click">
   <BeginStoryboard>
    <Storyboard
    TargetName="mesh" TargetProperty="Curl">
     <DoubleAnimation
      AccelerationRatio="0.5" 
      From="0" To="3"
      Duration="0:0:0.35"
       
     />
    </Storyboard>
   </BeginStoryboard>
  </EventTrigger>
  <EventTrigger SourceName="openBtn" RoutedEvent="Button.Click">
   <BeginStoryboard>
    <Storyboard
    TargetName="mesh" TargetProperty="Curl">
     <DoubleAnimation 
      DecelerationRatio="1" 
      From="3" To="0"
      Duration="0:0:0.35"
       
     />
    </Storyboard>
   </BeginStoryboard>
  </EventTrigger>
 </Window.Triggers>
 <Grid>

  <interactive3D:TrackballDecorator>
    <interactive3D:Interactive3DDecorator>
     <Viewport3D>
      <!-- Add a camera to the scene -->
      <Viewport3D.Camera>
       <PerspectiveCamera Position="0,0,10" LookDirection="0,0,-1" FieldOfView="60" />
      </Viewport3D.Camera>

       <meshTest:InteractiveMesh x:Name="mesh" Visual="{StaticResource UserInterface}"
                   Transform="{StaticResource Transform1}"
                  >
        <meshTest:InteractiveMesh.Curl>
         <Binding ElementName="mySlider" Path="Value" Mode="TwoWay"/>
        </meshTest:InteractiveMesh.Curl>
        <meshTest:InteractiveMesh.Ripple>
         <Binding ElementName="rippleSlider" Path="Value" Mode="TwoWay"/>
        </meshTest:InteractiveMesh.Ripple>
        <meshTest:InteractiveMesh.RippleRadius>
         <Binding ElementName="rippleRadiusSlider" Path="Value" Mode="TwoWay"/>
        </meshTest:InteractiveMesh.RippleRadius>

       </meshTest:InteractiveMesh>
      <!-- Lights -->
      <ModelVisual3D>
       <ModelVisual3D.Content>
        <Model3DGroup>
         <Model3DGroup.Children>
          <AmbientLight Color="sc# 1,0.25,0.25,0.25" />
          <DirectionalLight Color="sc# 1,0.75,0.75,0.75" Direction="0,0,-1"/>
         </Model3DGroup.Children>
        </Model3DGroup>
       </ModelVisual3D.Content>
      </ModelVisual3D>
     </Viewport3D>
    </interactive3D:Interactive3DDecorator>
   </interactive3D:TrackballDecorator>
  <Slider Name="mySlider" Value="0.5" HorizontalAlignment="Right" Minimum="0" Maximum="6" Margin="0,8,8,0" VerticalAlignment="Top" Width="138" Height="20" />
  <Slider Name="rippleSlider" Value="0.1" HorizontalAlignment="Right" Minimum="0" Maximum="2" Margin="0,28,8,0" VerticalAlignment="Top" Width="138" Height="20" />
  <Slider Name="rippleRadiusSlider" Value="0.1" HorizontalAlignment="Right" Minimum="0" Maximum="1" Margin="0,48,8,0" VerticalAlignment="Top" Width="138" Height="20" />
  <Button Margin="0,68,20,0" Name="closeBtn" Height="23" VerticalAlignment="Top" HorizontalAlignment="Right" Width="75">_Close</Button>
  <Button Margin="0,68,100,0" Name="openBtn" Height="23" VerticalAlignment="Top" HorizontalAlignment="Right" Width="75">_Open</Button>



 </Grid>
</Window>

TestDialog.xaml:

<UserControl x:Class="MeshDeformation.TestDialog"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="Auto" Width="Auto">
 
  <Grid Width="300" Height="300" Background="#FFFFD0D0" >
   <Rectangle Fill="#FFFFFFFF" Stroke="#FF000000" Margin="20,20,20,60" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
   <TextBox SnapsToDevicePixels="False" MinHeight="26" MinWidth="160" Margin="20,20,20,60" Name="textBox1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
       Text="Now is the time for all good men to come..." OverridesDefaultStyle="False" Background="{x:Null}" BorderBrush="{x:Null}" MaxLines="500" TextWrapping="Wrap"/>
  <Button SnapsToDevicePixels="False" Margin="0,0,20,20" Name="button1" Height="23" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="75">Test</Button>
  	<Ellipse Fill="#FFE20909" Stroke="#FF000000" HorizontalAlignment="Left" Margin="33,50,0,20" Height="35" Width="35"/>
  	<Expander HorizontalAlignment="Left" Margin="33,0,0,20" VerticalAlignment="Bottom" Width="87" Height="23" Header="Expander">
  		<Grid/>
  	</Expander>
 </Grid>
</UserControl>

InteractiveMesh.cs

using System;
using System.Collections.Generic;
using System.Text;

  //---------------------------------------------------------------------------
  //
  // (c) Copyright Microsoft Corporation.
  // This source is subject to the Microsoft Limited Permissive License.
  // See http://www.microsoft.com/resources/sharedsource/licensingbasics/limitedpermissivelicense.mspx
  // All other rights reserved.
  //
  // This file is part of the 3D Tools for Windows Presentation Foundation
  // project. For more information, see:
  // 
  // http://CodePlex.com/Wiki/View.aspx ProjectName=3DTools
  //
  //---------------------------------------------------------------------------

  using System;
  using System.Windows;
  using System.Windows.Media;
  using System.Windows.Media.Media3D;
  using _3DTools;

namespace MeshDeformation
{
  public class InteractiveMesh : InteractiveVisual3D
  {
    public InteractiveMesh()
    {
      Geometry = Tessellate();
      RotateTransform3D rcw = new RotateTransform3D(
        new AxisAngleRotation3D(new Vector3D(0,0,1),90)
        );
      RotateTransform3D rccw = new RotateTransform3D(
        new AxisAngleRotation3D(new Vector3D(0, 0, 1), -90)
        );
      transforms.Add(
        delegate(Point3D pt)
        {
          return rcw.Transform(pt);
        }
      );
      transforms.Add(ApplyCurl);
      transforms.Add(
        delegate(Point3D pt)
        {
          return rccw.Transform(pt);
        }
      );
    }


    internal double DegToRad(double degrees)
    {
      return (degrees / 180.0) * Math.PI;
    }

    public delegate Point3D TransformDelegate(Point3D value);

    List<TransformDelegate> transforms = new List<TransformDelegate>();

    internal Point3D GetPosition(double x, double y, double z)
    {
      Point3D pt = new Point3D(x,y,z);
      foreach (TransformDelegate d in transforms)
      {
        pt = d(pt);
      }
      return pt;
    }
    double spiralVal = Math.Log(0.5) / (2 * Math.PI);
    private Point3D ApplyCurl(Point3D value)
    {
      if (Curl < 0.00001)
      {
        return value;
      }
      double circumference = 2 / Curl;
      double radius = circumference / (2 * Math.PI);
      double maxTheta = 2*Math.PI*Curl; // 1 unit along the radius.
      double theta = (value.X*0.5+0.5)*maxTheta;
      double m = Math.Exp(theta * spiralVal);

      double mCos = m * Math.Sin(theta);
      double mSin = m * Math.Sin(theta);

      double x2 = -1 + m*Math.Sin(theta) * (radius-value.Z);
      double z2 = radius- m*Math.Cos(theta) * (radius-value.Z) ;
      return new Point3D(x2, value.Y, z2);
    }


    private Vector3D GetNormal(double x, double y, double curl)
    {
      if (curl < 0.00001)
      {
        return new Vector3D(0, 0, -1);
      }
      double radius = 1 / curl;
      double maxTheta = 2 / (radius); // 1 unit along the radius.
      double theta = (x * 0.5 + 0.5) * maxTheta;
      double x2 = Math.Sin(theta);
      double z2 = Math.Cos(theta);
      return new Vector3D(-x2, 0, -z2);

       /*
      double x = 2 * Math.Cos(t);
      double z = 2 * Math.Sin(t);
      return new Vector3D(x,1,z);

      //return new Vector3D(0,0,1);
       */
    }


    private Point GetTextureCoordinate(double x, double y)
    {
      return new Point((x * 0.5+0.5),y * -0.5 + 0.5);
      /*
      // return new Point(t, y*-0.5+0.5);

      return new Point(1.0 - t * 1 / (2 * Math.PI), y * -0.5 + 0.5);
       */
    }



    public double Curl
    {
      get { return (double)GetValue(CurlProperty); }
      set { SetValue(CurlProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Curl. This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CurlProperty =
      DependencyProperty.Register("Curl", typeof(double), typeof(InteractiveMesh), new UIPropertyMetadata(0.1,new PropertyChangedCallback(CurlChanged)));


    static private void CurlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      InteractiveMesh mesh = (InteractiveMesh)d;
      mesh.CurlChanged(e);
    }

    private void Retesselate()
    {
      Geometry = Tessellate();

    }
    private void CurlChanged(DependencyPropertyChangedEventArgs e)
    {
      Retesselate();
    }
    internal MeshGeometry3D Tessellate()
    {
      double curl = this.Curl;
      int tDiv = 32;
      int yDiv = 32;
      double minX = -1.0;
      double maxX = 1.0;
      double minY = -1.0;
      double maxY = 1.0;

      double dx = (maxX-minX) / tDiv;
      double dy = (maxY - minY) / yDiv;

      MeshGeometry3D mesh = new MeshGeometry3D();
      // front faces.
      for (int yi = 0; yi <= yDiv; yi++)
      {
        double y = minY + yi * dy;

        for (int ti = 0; ti <= tDiv; ti++)
        {
          double x = minX + ti * dx;
          Point3D pt = GetPosition(x, y,0);
          mesh.Positions.Add(pt);

          Vector3D normal = GetPosition(x, y, 0.0001)-pt;
          normal.Normalize();
          mesh.Normals.Add(normal);
          mesh.TextureCoordinates.Add(GetTextureCoordinate(x, y));
        }
      }

      for (int yi = 0; yi < yDiv; yi++)
      {
        for (int ti = 0; ti < tDiv; ti++)
        {
          int x0 = ti;
          int x1 = (ti + 1);
          int y0 = yi * (tDiv + 1);
          int y1 = (yi + 1) * (tDiv + 1);

          mesh.TriangleIndices.Add(x0 + y1);
          mesh.TriangleIndices.Add(x0 + y0);
          mesh.TriangleIndices.Add(x1 + y0);

          mesh.TriangleIndices.Add(x0 + y1);
          mesh.TriangleIndices.Add(x1 + y0);
          mesh.TriangleIndices.Add(x1 + y1);


        }
      }
#if !***
      // Back faces
      int backfaceBase = (tDiv+1)*(yDiv+1);
      for (int yi = 0; yi <= yDiv; yi++)
      {
        double y = minY + yi * dy;

        for (int ti = 0; ti <= tDiv; ti++)
        {
          double x = minX + ti * dx;

          Point3D pt = GetPosition(x, y, 0);
          mesh.Positions.Add(pt);

          Vector3D normal = GetPosition(x, y, -0.0001) - pt;
          normal.Normalize();
          mesh.Normals.Add(normal);

          mesh.TextureCoordinates.Add(GetTextureCoordinate(x, y));
        }
      }

      for (int yi = 0; yi < yDiv; yi++)
      {
        for (int ti = 0; ti < tDiv; ti++)
        {
          int x0 = ti;
          int x1 = (ti + 1);
          int y0 = yi * (tDiv + 1) + backfaceBase;
          int y1 = (yi + 1) * (tDiv + 1) + backfaceBase;

          mesh.TriangleIndices.Add(x0 + y0);
          mesh.TriangleIndices.Add(x0 + y1);
          mesh.TriangleIndices.Add(x1 + y0);

          mesh.TriangleIndices.Add(x1 + y0);
          mesh.TriangleIndices.Add(x0 + y1);
          mesh.TriangleIndices.Add(x1 + y1);


        }
      }
      //ApplyRipple(mesh);
#endif
      mesh.Freeze();
      return mesh;
    }



    public double RippleRadius
    {
      get { return (double)GetValue(RippleRadiusProperty); }
      set { SetValue(RippleRadiusProperty, value); }
    }

    private static void RippleRadiusChanged(object o, DependencyPropertyChangedEventArgs e)
    {
      ((InteractiveMesh)o).RippleRadiusChanged(e);
    }
    private void RippleRadiusChanged(DependencyPropertyChangedEventArgs e)
    {
      Retesselate();
    }

    // Using a DependencyProperty as the backing store for RippleRadius. This enables animation, styling, binding, etc...
    public static readonly DependencyProperty RippleRadiusProperty =
      DependencyProperty.Register("RippleRadius", typeof(double), typeof(InteractiveMesh), new UIPropertyMetadata(0.1,new PropertyChangedCallback(RippleRadiusChanged)));


    public double Ripple
    {
      get { return (double)GetValue(RippleProperty); }
      set { SetValue(RippleProperty, value); }
    }

    private static void RippleChanged(object o, DependencyPropertyChangedEventArgs e)
    {
      ((InteractiveMesh)o).RippleChanged(e);
    }
    private void RippleChanged(DependencyPropertyChangedEventArgs e)
    {
      Retesselate();
    }

    
    // Using a DependencyProperty as the backing store for Ripple. This enables animation, styling, binding, etc...
    public static readonly DependencyProperty RippleProperty =
      DependencyProperty.Register("Ripple", typeof(double), typeof(InteractiveMesh), new UIPropertyMetadata(0.1,new PropertyChangedCallback(RippleChanged)));



    void ApplyRipple(ref Point3D pt, ref Vector3D norm)
    {
      double d = Math.Sqrt(pt.X * pt.X + pt.Y * pt.Y + pt.Z * pt.Z)*RippleRadius;
      double v;
      if (Math.Abs(d) < 0.0001) 
      {
        v = Ripple*1.0;
      } else {
        v = Ripple * Math.Sin(d * Math.PI * 2) / d;
      }
      
      pt.Z += v;
    }
    private void ApplyRipple(MeshGeometry3D mesh)
    {
      for (int i = 0; i < mesh.Positions.Count; ++i)
      {
        Point3D pos = mesh.PositionsIdea;
        Vector3D norm = mesh.NormalsIdea;
        ApplyRipple(ref pos, ref norm);
        mesh.PositionsIdea = pos;
        mesh.NormalsIdea = norm;
      }
    }
  }
}





Re: Windows Presentation Foundation (WPF) Odd aliasing effects with Interactive3DDecorator

Robin E. Davies

Read Idea as [ i ]. I tried. I really did.





Re: Windows Presentation Foundation (WPF) Odd aliasing effects with Interactive3DDecorator

Kurt Berglund - MSFT

Just since its been awhile since you posted, I thought I'd reply back and confirm I got your new post. I just got back from a holiday vacation and plan on looking at your new post sometime this week.