My linear algebra is a little rusty be confident in helping, but this article looks like it could be a good starting place for you
After lots of playing with this I actually concur with "Ladislav Mrnka"'s matrix answer as being the simplest solution and have up-voted their answer.
Just leaving the sample below to give you something to play with but you will need to update it via a Matrix3DProjection.
It looks like you are treating your source picture as having one of several possible fields of view, e.g. as if taken with a wide-angle lens for the 120° or a zoom lens for the 30°. You are then trying to reproduce the aspect ratio of the original scene when displayed. Is this correct?
If so the procedure you actually want to stretch the picture horizontally to restore the implicit width, before rotating it with the perspective transform. That would mean you are actually trying to solve 2 separate (simpler) maths problems here e.g.:
- Calculate the display width of the image based on FOV, aspect ratio & width (using X-Scaling).
- Calculate the rotation desired to fit a perspective transform of a given width within a desired display width (projection rotation about Y axis).
The difficulty I have is that the example photos do not indicate any specific rules about the display. The display widths all vary so I cannot work out what your end result is intended to be. If you can provide more information I should be able to provide specific calculations.
Ok, based on your use of the Perspective settings in PowerPoint, the 2 required steps are indeed:
- Scale the horizontal size (according to your "X"-angle)
- Apply a projection transform to emulate the perspective angle in PowerPoint
The first calculation is very simple. You need to set the scale to Cosine(X-angle). The second is an estimate as the Powerpoint perspective angle does not seem to relate to rotation.
I have provided a full sample XAML and code-behind below to generate the app shown*.
**Note: there is a serious flaw in that Projection Transforms are not able to distort the image to the degree you require. I am trying Matrix3DProjection instead, solution will follow *
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="PerspectivePhotosTest.PerspectivePhotos"
d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Image x:Name="SampleImage" Height="101" Source="Image1.png" Stretch="Fill" VerticalAlignment="Center" HorizontalAlignment="Center" Width="128" RenderTransformOrigin="0.5,0.5">
<Image.Projection>
<PlaneProjection x:Name="Rotation" RotationY="0"/>
</Image.Projection>
<Image.RenderTransform>
<CompositeTransform x:Name="Scale" ScaleX="1"/>
</Image.RenderTransform>
</Image>
<Grid HorizontalAlignment="Left" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text=""X-Angle"" VerticalAlignment="Top" HorizontalAlignment="Right"/>
<TextBox x:Name="XAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=XValueSlider}" TextChanged="XAngleTextBox_TextChanged"/>
<Slider x:Name="XValueSlider" Grid.Row="1" Grid.ColumnSpan="2" LargeChange="10" Maximum="80" SmallChange="1"/>
<TextBlock Text="Perspective Angle" VerticalAlignment="Top" Grid.Row="2" HorizontalAlignment="Right"/>
<TextBox x:Name="PerspectiveAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=PerspectiveSlider}" TextChanged="PerspectiveAngleTextBox_TextChanged"/>
<Slider x:Name="PerspectiveSlider" Grid.Row="3" Grid.ColumnSpan="2" Maximum="120" SmallChange="1" LargeChange="10"/>
</Grid>
</StackPanel>
</Grid>
</UserControl>
Code behind:
using System;
using System.Windows.Controls;
namespace PerspectivePhotosTest
{
public partial class PerspectivePhotos : UserControl
{
public PerspectivePhotos()
{
InitializeComponent();
}
private void XAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
Scale.ScaleX = CalcScale(DegreeToRadian(double.Parse(XAngleTextBox.Text)));
}
private void PerspectiveAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
Rotation.RotationY = CalcTransform(double.Parse(PerspectiveAngleTextBox.Text));
}
private double CalcScale(double angleInRadians)
{
return Math.Cos(angleInRadians) * 2 + 0.3;
}
private double CalcTransform(double angleInDegrees)
{
return angleInDegrees / 2;
}
private double DegreeToRadian(double angle)
{
return Math.PI * angle / 180.0;
}
}
}
This should give you a handle test framework to try out variations. I re-factored the calculation steps to make it more obvious.
Use Matrix3DProjection and set transformation based on matrix provided at the end of this function from Direct3D. You need your FOV in radians, Aspect ratio for screen and two distances for clipping (you are defining finite frustrum). If are you looking for futher explanation why it is set this way you should get some book about computer graphics. Also it is usual that matrix for projection transformation sets only view frustrum. Rotatin objects around X axis is performed by separate transformation but it is general practice from computer graphics and I'm not sure if it works same in Silverlight.
Edit:
If you need to use both rotation and projection in single matrix try to use this one:
xScale 0 0 0 0 cos(X)*yScale -sin(X)*z*zf/(zf-zn) -sin(X) 0 sin(X)*yScale cox(X)*z*zf/(zf-zn) cos(X) 0 0 (-zn*zf)/(zf-zn) 0
Where X in cos(X) and sin(X) is your rotation around X asis in radians
z is translation in Z direction because you will have to move with your image to see it whole.
yScale = cot(FOV/2) FOV is in radians
xScale = yScale/aspectRatio Aspect ratio is defined by height and width of panel used for image rendering
zn = Z near - everything before this is clipped. zf = Z far - evrything after this is clipped. Be aware that z coordinate of image has to be between those two.
It is long time since I did this last time so I hope I have computed transformation correctly. Matrix multiplication should be correct but there is a chance that I multiplyed it in wrong order.
Edit2:
My previous suggestion doesn't work. First matrices used for computation are incorrect because Silverlight uses transposed versions. Second translation image to center and viewport transformation is not used. I have combined Alison's proposed code (also can be found here) with modification to have FovX and HiTech Magic's Silverlight application. I have never written Silverlight app before ... Here is working sample:
<Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid x:Name="Canvas" Background="White" Height="150" Width="200">
<Image x:Name="SampleImage" Source="Penguins.jpg" Stretch="Fill" VerticalAlignment="Center" HorizontalAlignment="Center" Height="120" Width="160" RenderTransformOrigin="0.5,0.5" >
<Image.Projection>
<Matrix3DProjection x:Name="Matrix" ProjectionMatrix="1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1"/>
</Image.Projection>
</Image>
</Grid>
<Grid HorizontalAlignment="Center" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text=""X-Angle"" VerticalAlignment="Top" HorizontalAlignment="Right"/>
<TextBox x:Name="XAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=XValueSlider}" TextChanged="XAngleTextBox_TextChanged"/>
<Slider x:Name="XValueSlider" Grid.Row="1" Grid.ColumnSpan="2" LargeChange="10" Maximum="80" SmallChange="1"/>
<TextBlock Text="Perspective Angle" VerticalAlignment="Top" Grid.Row="2" HorizontalAlignment="Right"/>
<TextBox x:Name="PerspectiveAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=PerspectiveSlider}" TextChanged="PerspectiveAngleTextBox_TextChanged" />
<Slider x:Name="PerspectiveSlider" Grid.Row="3" Grid.ColumnSpan="2" Maximum="120" SmallChange="1" LargeChange="10" Value="60"/>
</Grid>
</StackPanel>
</Grid>
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void UpdateMatrix()
{
double val;
double X = double.TryParse(XAngleTextBox.Text, NumberStyles.Any,
CultureInfo.InvariantCulture, out val) ? val : 0.0;
double FOV = double.TryParse(PerspectiveAngleTextBox.Text, NumberStyles.Any,
CultureInfo.InvariantCulture, out val) ? val : 0.0;
ApplyProjection(FOV, X);
}
private double DegreeToRadian(double angle)
{
return Math.PI * angle / 180.0;
}
private void XAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
UpdateMatrix();
}
private void PerspectiveAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
UpdateMatrix();
}
private void ApplyProjection(double FOV, double X)
{
// Translate the image along the negative Z-axis such that it occupies 50% of the
// vertical field of view.
double fov = DegreeToRadian(FOV);
double translationZ = -SampleImage.Width / Math.Tan(fov / 2.0);
double theta = DegreeToRadian(X);
// You can create a 3D effect by creating a number of simple
// tranformation Matrix3D matrixes and then multiply them together.
Matrix3D centerImageAtOrigin = TranslationTransform(
-SampleImage.Width / 2.0,
-SampleImage.Height / 2.0, 0);
Matrix3D invertYAxis = CreateScaleTransform(1.0, -1.0, 1.0);
Matrix3D rotateAboutY = RotateYTransform(theta);
Matrix3D translateAwayFromCamera = TranslationTransform(0, 0, translationZ);
Matrix3D perspective = PerspectiveTransformFovRH(fov,
Canvas.ActualWidth / Canvas.ActualHeight,
1.0, // near plane
1000.0); // far plane
Matrix3D viewport = ViewportTransform(Canvas.ActualWidth, Canvas.ActualHeight);
Matrix3D m = centerImageAtOrigin * invertYAxis;
m = m * rotateAboutY;
m = m * translateAwayFromCamera;
m = m * perspective;
m = m * viewport;
Matrix3DProjection m3dProjection = new Matrix3DProjection();
m3dProjection.ProjectionMatrix = m;
SampleImage.Projection = m3dProjection;
}
private Matrix3D TranslationTransform(double tx, double ty, double tz)
{
Matrix3D m = new Matrix3D();
m.M11 = 1.0; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
m.M21 = 0.0; m.M22 = 1.0; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
m.OffsetX = tx; m.OffsetY = ty; m.OffsetZ = tz; m.M44 = 1.0;
return m;
}
private Matrix3D CreateScaleTransform(double sx, double sy, double sz)
{
Matrix3D m = new Matrix3D();
m.M11 = sx; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
m.M21 = 0.0; m.M22 = sy; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = 0.0; m.M32 = 0.0; m.M33 = sz; m.M34 = 0.0;
m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
return m;
}
private Matrix3D RotateYTransform(double theta)
{
double sin = Math.Sin(theta);
double cos = Math.Cos(theta);
Matrix3D m = new Matrix3D();
m.M11 = cos; m.M12 = 0.0; m.M13 = -sin; m.M14 = 0.0;
m.M21 = 0.0; m.M22 = 1.0; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = sin; m.M32 = 0.0; m.M33 = cos; m.M34 = 0.0;
m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
return m;
}
private Matrix3D RotateZTransform(double theta)
{
double cos = Math.Cos(theta);
double sin = Math.Sin(theta);
Matrix3D m = new Matrix3D();
m.M11 = cos; m.M12 = sin; m.M13 = 0.0; m.M14 = 0.0;
m.M21 = -sin; m.M22 = cos; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
return m;
}
private Matrix3D PerspectiveTransformFovRH(double fieldOfView, double aspectRatio, double zNearPlane, double zFarPlane)
{
double width = 1.0 / Math.Tan(fieldOfView / 2.0);
double height = width * aspectRatio;
double d = zNearPlane - zFarPlane;
Matrix3D m = new Matrix3D();
m.M11 = width; m.M12 = 0; m.M13 = 0; m.M14 = 0;
m.M21 = 0; m.M22 = height; m.M23 = 0; m.M24 = 0;
m.M31 = 0; m.M32 = 0; m.M33 = zFarPlane / d; m.M34 = -1;
m.OffsetX = 0; m.OffsetY = 0; m.OffsetZ = zNearPlane * zFarPlane / d; m.M44 = 0;
return m;
}
private Matrix3D ViewportTransform(double width, double height)
{
Matrix3D m = new Matrix3D();
m.M11 = width / 2.0; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
m.M21 = 0.0; m.M22 = -height / 2.0; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
m.OffsetX = width / 2.0; m.OffsetY = height / 2.0; m.OffsetZ = 0.0; m.M44 = 1.0;
return m;
}
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
UpdateMatrix();
}
}
Okay. I have combined Ladislav Mrnka's Matrix transform answer with my previous sample app, but there appear to have been some typos in their 3D Matrix example and I am not strong enough with 3D Matrix math to correct it. The end result is a blank display where the image should be :(
Instead to get this party started I have provide a full interactive test app (below) with Xaml so that Ladislav Mrnka (or someone else with better 3D maths) can correct the problem.
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Image x:Name="SampleImage" Height="101" Source="Image1.png" Stretch="Fill" VerticalAlignment="Center" HorizontalAlignment="Center" Width="128" RenderTransformOrigin="0.5,0.5">
<Image.Projection>
<Matrix3DProjection x:Name="Matrix" ProjectionMatrix="1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1"/>
</Image.Projection>
</Image>
<Grid HorizontalAlignment="Left" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text=""X-Angle"" VerticalAlignment="Top" HorizontalAlignment="Right"/>
<TextBox x:Name="XAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=XValueSlider}" TextChanged="XAngleTextBox_TextChanged"/>
<Slider x:Name="XValueSlider" Grid.Row="1" Grid.ColumnSpan="2" LargeChange="10" Maximum="80" SmallChange="1"/>
<TextBlock Text="Perspective Angle" VerticalAlignment="Top" Grid.Row="2" HorizontalAlignment="Right"/>
<TextBox x:Name="PerspectiveAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=PerspectiveSlider}" TextChanged="PerspectiveAngleTextBox_TextChanged"/>
<Slider x:Name="PerspectiveSlider" Grid.Row="3" Grid.ColumnSpan="2" Maximum="120" SmallChange="1" LargeChange="10"/>
<TextBlock Text="Z-Near" VerticalAlignment="Top" Grid.Row="4" HorizontalAlignment="Right"/>
<TextBox x:Name="ZNearTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=ZNearSlider}" TextChanged="ZNearTextBox_TextChanged"/>
<Slider x:Name="ZNearSlider" Grid.Row="5" Grid.ColumnSpan="2" Minimum="-1000" Maximum="1000" SmallChange="1" LargeChange="10"/>
<TextBlock Text="Z-Far" VerticalAlignment="Top" Grid.Row="6" HorizontalAlignment="Right"/>
<TextBox x:Name="ZFarTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Row="6" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=ZFarSlider}" TextChanged="ZFarTextBox_TextChanged"/>
<Slider x:Name="ZFarSlider" Grid.Row="7" Grid.ColumnSpan="2" Minimum="-1000" Maximum="1000" SmallChange="1" LargeChange="10"/>
</Grid>
</StackPanel>
</Grid>
Code-behind:
using System;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace PerspectivePhotoTest
{
public partial class PerspectivePhotosMatrix : UserControl
{
public PerspectivePhotosMatrix()
{
InitializeComponent();
}
private void UpdateMatrix()
{
double X = DegreeToRadian(double.Parse(XAngleTextBox.Text));
double xScale = X;
double FOV = DegreeToRadian(double.Parse(PerspectiveAngleTextBox.Text));
double yScale = Math.Cos(FOV / 2) / Math.Sin(FOV / 2);
double zn = double.Parse(ZNearTextBox.Text);
double zf = double.Parse(ZFarTextBox.Text);
double z = 0;
Matrix.ProjectionMatrix = new Matrix3D(xScale, 0, 0, 0,
0, Math.Cos(X) * yScale, -Math.Sin(X) * z * zf / (zf - zn), -Math.Sin(X),
0, Math.Sin(X) * yScale, Math.Cos(X) * z * zf / (zf - zn), Math.Cos(X),
0,0,(-zn*zf)/(zf-zn), 0);
}
private double DegreeToRadian(double angle)
{
return Math.PI * angle / 180.0;
}
private void XAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
UpdateMatrix();
}
private void PerspectiveAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
UpdateMatrix();
}
private void ZNearTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
UpdateMatrix();
}
private void ZFarTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
UpdateMatrix();
}
}
}
I think the problem everyone is encountering is that there needs to be a viewport shift along with the perspective transformation.
Try this out:
private void ApplyProjection()
{
double fovY = FOV * Math.PI / 180 / 2.0;
double translationZ = -image1.ActualHeight / Math.Tan(fovY / 2.0);
double theta = YourAngleX * Math.PI / 180.0;
Matrix3D centerImageAtOrigin = TranslationTransform(
-image1.ActualWidth / 2.0,
-image1.ActualHeight / 2.0, 0);
Matrix3D invertYAxis = CreateScaleTransform(1.0, -1.0, 1.0);
Matrix3D rotateAboutY = RotateYTransform(theta);
Matrix3D translateAwayFromCamera = TranslationTransform(0, 0, translationZ);
Matrix3D perspective = PerspectiveTransformFovRH(fovY,
image1.ActualWidth / image1.ActualHeight, // aspect ratio
1.0, // near plane
1000.0); // far plane
Matrix3D viewport = ViewportTransform(image1.ActualWidth, image1.ActualHeight);
Matrix3D m = centerImageAtOrigin * invertYAxis;
m = m * rotateAboutY;
m = m * translateAwayFromCamera;
m = m * perspective;
m = m * viewport;
Matrix3DProjection m3dProjection = new Matrix3DProjection();
m3dProjection.ProjectionMatrix = m;
image1.Projection = m3dProjection;
}
private Matrix3D TranslationTransform(double tx, double ty, double tz)
{
Matrix3D m = new Matrix3D();
m.M11 = 1.0; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
m.M21 = 0.0; m.M22 = 1.0; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
m.OffsetX = tx; m.OffsetY = ty; m.OffsetZ = tz; m.M44 = 1.0;
return m;
}
private Matrix3D CreateScaleTransform(double sx, double sy, double sz)
{
Matrix3D m = new Matrix3D();
m.M11 = sx; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
m.M21 = 0.0; m.M22 = sy; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = 0.0; m.M32 = 0.0; m.M33 = sz; m.M34 = 0.0;
m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
return m;
}
private Matrix3D RotateYTransform(double theta)
{
double sin = Math.Sin(theta);
double cos = Math.Cos(theta);
Matrix3D m = new Matrix3D();
m.M11 = cos; m.M12 = 0.0; m.M13 = -sin; m.M14 = 0.0;
m.M21 = 0.0; m.M22 = 1.0; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = sin; m.M32 = 0.0; m.M33 = cos; m.M34 = 0.0;
m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
return m;
}
private Matrix3D RotateZTransform(double theta)
{
double cos = Math.Cos(theta);
double sin = Math.Sin(theta);
Matrix3D m = new Matrix3D();
m.M11 = cos; m.M12 = sin; m.M13 = 0.0; m.M14 = 0.0;
m.M21 = -sin; m.M22 = cos; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
return m;
}
private Matrix3D PerspectiveTransformFovRH(double fieldOfViewY, double aspectRatio, double zNearPlane, double zFarPlane)
{
double height = 1.0 / Math.Tan(fieldOfViewY / 2.0);
double width = height / aspectRatio;
double d = zNearPlane - zFarPlane;
Matrix3D m = new Matrix3D();
m.M11 = width; m.M12 = 0; m.M13 = 0; m.M14 = 0;
m.M21 = 0; m.M22 = height; m.M23 = 0; m.M24 = 0;
m.M31 = 0; m.M32 = 0; m.M33 = zFarPlane / d; m.M34 = -1;
m.OffsetX = 0; m.OffsetY = 0; m.OffsetZ = zNearPlane * zFarPlane / d; m.M44 = 0;
return m;
}
private Matrix3D ViewportTransform(double width, double height)
{
Matrix3D m = new Matrix3D();
m.M11 = width / 2.0; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
m.M21 = 0.0; m.M22 = -height / 2.0; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
m.OffsetX = width / 2.0; m.OffsetY = height / 2.0; m.OffsetZ = 0.0; m.M44 = 1.0;
return m;
}
This will create the appropriate perspective shift and match what PowerPoint is producing.
This code was adapted from MSDN.