F# WPF - displaying a simple object in Viewport3D - wpf

I'm trying to get into generating 3D graphics in F# (as was apparent from my previous question) with very little time and very little initial knowledge of F#. I'm studying Tomas Petricek's fractal example, but I can't really make heads or tails of it. I've managed to define a window with a Viewport3D object in XAML, initialize and display it from F#. But as far as creating 3d objects in F# and displaying them goes, I'm lost in a sea of fractal generation, coordinate translation, and other calculations.
Could somebody provide a simple example, of creating one really simple object in F# (a single cube, or just a triangle) and display it in the WPF window? That would be huge help.
Thank you.

Here's a simple example with two triangles making a single square:
#if INTERACTIVE
#r "PresentationCore"
#r "PresentationFramework"
#r "WindowsBase"
#r "System.Xaml"
#endif
open System.Windows
open System.Windows.Controls
open System.Windows.Media
open System.Windows.Media.Media3D
let grp = Model3DGroup()
let geo = MeshGeometry3D()
// Point collection
for x,y,z in [0.5, 0.0, 0.0;
1.0, 0.0, 0.0;
0.5, 0.5, 0.0;
1.0, 0.5, 0.0] do
geo.Positions.Add(Point3D(x,y,z))
// First triangle
for i in [0;1;2] do geo.TriangleIndices.Add(i)
// Second triangle - order matters for deciding front vs. back
for i in [2;1;3] do geo.TriangleIndices.Add(i)
// Create a model with the mesh and a front and back material
let model =
GeometryModel3D(
Geometry = geo,
Material = DiffuseMaterial(Brushes.Black),
BackMaterial = DiffuseMaterial(Brushes.Red))
grp.Children.Add(model)
// add light so back color is visible
grp.Children.Add(AmbientLight())
// set up a continuous rotation around the y-axis
let rotation = AxisAngleRotation3D(Axis = Vector3D(0.,1.,0.))
let anim =
Animation.DoubleAnimation(0.0, 360., Duration(System.TimeSpan.FromSeconds 2.),
RepeatBehavior = Animation.RepeatBehavior.Forever)
rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, anim)
// apply the rotation to the geometry
grp.Transform <- RotateTransform3D(rotation)
// create a camera pointing at the triangle
let cam = PerspectiveCamera(Point3D(0.,0.,2.), Vector3D(0., 0., -1.), Vector3D(0., 1., 0.), 60.)
// set the viewport up with the camera and geometry
let vprt = Viewport3D(Camera = cam)
vprt.Children.Add(ModelVisual3D(Content = grp))
// add the viewport to a window
let wnd = Window(Content = vprt, Title = "3D", Visibility = Visibility.Visible)

Related

F# WPF script, draw graphics on button click

I am tinkering with using F# scripts and I'm just wanting to draw lines on a blank Windows Form with a simple button click. Hopefully you can see what I'm trying to do here:
open System.Drawing
open System.Windows.Forms
let form = new Form(Width = 400, Height = 400, Text = "draw test")
let panel = new FlowLayoutPanel()
form.Controls.Add(panel)
let paint(e : PaintEventArgs) =
let pen = new Pen(Color.Black);
e.Graphics.DrawLine(pen, new PointF(100.0f, 100.0f), new PointF(200.0f, 200.0f))
let button = new Button()
button.Text <- "Click to draw"
button.AutoSize <- true
button.Click.Add(fun _ -> form.Paint.Add(paint)) // <- does not draw a line on click
panel.Controls.Add(button)
//form.Paint.Add(paint) <- here, if uncommented, it will draw a line when the script is run
form.Show()
If I take the form.Paint.Add(paint) uncomment it above form.Show(), then of course it will draw on the form, but I'm trying to do it with a button click. It's not exactly clear to me how to make this happen in a script like this, and I've been scouring all over for a similar example in F#. Any help would be appreciated.
If you add your Paint event handler before the form is drawn for the first time, then it will draw using that handler.
If you add it after, you need to make sure the form then redraws itself. You could for instance call Refresh or Invalidate on it.
Ex.:
button.Click.Add(fun _ -> form.Paint.Add(paint); form.Invalidate())
Originally an edit, I moved it to the answer section:
Okay, so I was confused about the difference between WPF and Winforms due to the fact that I have seen the terms used together various places... #Asik has added an answer for Winforms, but here I have slapped together a working .fsx script specifically for WPF based on several FSharp Snippets (as well as several Google searches) which can also be compiled if so desired. I'll update this as needed or requested. Also, just to point out, the whole motivation behind this is to be able to quickly test drawing graphics via FSI.
#r #"PresentationCore"
#r #"PresentationFramework"
#r #"WindowsBase"
#r #"System.Xaml"
#r #"UIAutomationTypes"
open System
open System.Windows
open System.Windows.Media
open System.Windows.Shapes
open System.Windows.Controls
let window = Window(Height = 400.0, Width = 400.0)
window.Title <- "Draw test"
let stackPanel = StackPanel()
window.Content <- stackPanel
stackPanel.Orientation <- Orientation.Vertical
let button1 = Button()
button1.Content <- "Click me to draw a blue ellipse"
stackPanel.Children.Add button1
let button2 = Button()
button2.Content <- "Click me to draw a red ellipse"
stackPanel.Children.Add button2
let clearButton = Button()
clearButton.Content <- "Click me to clear the canvas"
stackPanel.Children.Add clearButton
let canvas = Canvas()
canvas.Width <- window.Width
canvas.Height <- window.Height
stackPanel.Children.Add canvas
let buildEllipse height width fill stroke =
let ellipse = Ellipse()
ellipse.Height <- height
ellipse.Width <- width
ellipse.Fill <- fill
ellipse.Stroke <- stroke
ellipse
let ellipse1 = buildEllipse 100.0 200.0 Brushes.Aqua Brushes.Black
Canvas.SetLeft(ellipse1, canvas.Width / 10.0) //messy, will fix at some point!
Canvas.SetTop(ellipse1, canvas.Height / 10.0)
let ellipse2 = buildEllipse 200.0 100.0 Brushes.Red Brushes.DarkViolet
Canvas.SetLeft(ellipse2, canvas.Width / 4.0)
Canvas.SetTop(ellipse2, canvas.Height / 5.0)
let addEllipseToCanvas (canvas:Canvas) (ellipse:Ellipse) =
match canvas.Children with
| c when c.Contains ellipse ->
canvas.Children.Remove ellipse
canvas.Children.Add(ellipse) |> ignore //needs to be removed and readded or the canvas complains
| _ ->
canvas.Children.Add(ellipse) |> ignore
button1.Click.Add(fun _ -> addEllipseToCanvas canvas ellipse1)
button2.Click.Add(fun _ -> addEllipseToCanvas canvas ellipse2)
clearButton.Click.Add(fun _ -> canvas.Children.Clear())
#if INTERACTIVE
window.Show()
#else
[<EntryPoint; STAThread>]
let main argv =
let app = new Application()
app.Run(window)
#endif

Imagebrush resolution to render a MeshGeometry3D in WPF

I am visualizing some spatial data using a MeshGeometry3D in WPF. I noticed that passing an ImageBrush to the constructor of DiffuseMaterial makes 3D visualization and manipulation fast and efficient (than say using a VisualBrush). My solution is in the following code block where this.dataMeshModel is a GeometryModel3D that includes the mesh. The problem is that I have to give very high resolutions (1000) to the Bitmap image source to make the grid look nice and distinguishable. Choosing this high dpi seems odd and easily raises memory exceptions. Any suggestion? The data model looks like this for 1000 dpi and this for 4000 dpi. THANKS!
private void assignTexture(Geometry gridGeom, double textureDPI, double gridThickness)
{
// rendering the grid on a DrawingVisual
DrawingVisual dv = new DrawingVisual();
using (var dvc = dv.RenderOpen())
{
dvc.DrawRectangle(Brushes.Tomato, null, gridGeom.Bounds);
dvc.DrawGeometry(null, new Pen(Brushes.Black, gridThickness), gridGeom);
}
if (dv.Drawing.CanFreeze)
{
dv.Drawing.Freeze();
}
//rendering the DrawingVisual to an image
Rect bounds = dv.ContentBounds;
RenderTargetBitmap renderedBitmap = new RenderTargetBitmap(
(int)(bounds.Width * this.textureDPI / 96),
(int)(bounds.Height * this.textureDPI / 96),
this.textureDPI,
this.textureDPI,
PixelFormats.Pbgra32);
renderedBitmap.Render(dv);
// adding the rendered image to create an imagebrush
ImageBrush imBrush = new ImageBrush(renderedBitmap);
//creating the material
DiffuseMaterial dataMeshMaterial = new DiffuseMaterial(imBrush);
this.dataMeshModel.Material = this.dataMeshMaterial;
}

WPF - How to work out how much of a canvas background image is cropped?

I have a canvas with a background image:
var bi = new BitmapImage(new Uri(imgLocFull));
var ib = new ImageBrush(bi) {Stretch = Stretch.UniformToFill};
MyCanvas.Background = ib;
I am overlaying various shapes on the image, and want the position of the shapes relative to the background image to be fixed.
If my application window is resized, the amount of the image that is cropped, horizontally and vertically, changes, and when my shapes are redrawn, they do not appear in the same position on the background image.
How can I determine how much of the image has been cropped (to apply an adjustment factor to the overlaid objects' positions?) Or is there a better way of fixing the location of a shape relative to the background image?
Here is my present drawing code:
var l = new Ellipse();
var scb = new SolidColorBrush();
scb.Color = Color.FromRgb(rCol, gCol, bCol);
l.Fill = scb;
l.StrokeThickness = 0;
l.Width = 3;
l.Height = 3;
Canvas.SetBottom(l, point.Y); // * clipping factor here?
Canvas.SetLeft(l, point.X); // * clipping factor here?
MyCanvas.Children.Add(l);
EDIT: Further Clarification
Here's a concrete example of what I am trying to achieve. My image is an aerial photograph, and I want to mark a particular geographical feature (with, say, an ellipse.)
When the window is resized, the ellipse doesn't stay on the feature, it stays relative to the left and top of the canvas.
I can get it closer to the right place by moving it using a factor (newx = newheight/oldheight * oldx) but this doesn't quite work because of the UniformToFill stretch mode, which sees some of the image clipped off the canvas.
The Top and Left of the Canvas are 'anchored', while the Bottom and Right move when resizing... try setting the Canvas.Top Attached Property instead, along with the Canvas.Left Attached Property as you are:
var l = new Ellipse();
var scb = new SolidColorBrush();
scb.Color = Color.FromRgb(rCol, gCol, bCol);
l.Fill = scb;
l.StrokeThickness = 0;
l.Width = 3;
l.Height = 3;
Canvas.SetTop(l, point.Y); // * clipping factor here?
Canvas.SetLeft(l, point.X); // * clipping factor here?
MyCanvas.Children.Add(l);
UPDATE >>>
You asked Or is there a better way of fixing the location of a shape relative to the background image?
I answered this question, so I don't understand why you would need to do anything else... your objects will not move when the screen in resized *if you only set the Grid.Top and Grid.Left properties.

UISplitViewController Detail View Drop shadow

I'm trying to set a shadow on my UISplitViewController's Detail View, that I want to be visible over the Master View, in iOS 6.
In my Detail ViewController:
self.view.layer.shadowColor = [[UIColor blackColor] CGColor];
self.view.layer.shadowOffset = CGSizeMake(-3.0f, 0.0f);
self.view.layer.shadowRadius = 3.0f;
self.view.layer.shadowOpacity = 1.0f;
self.view.layer.masksToBounds = NO;
self.view.clipsToBounds = NO;
However, the SplitVC automatically clips its sub-views, even when I set it to NO in the above code, and there is no shadow.
Can anyone let me know the correct way to achieve this?
The best way I found to do this is to add a 1px view to the master view controller and snap it to the right edge and apply a shadow to that view instead.
It seems that a superview some way down the hierarchy is performing the clipping.
Try this:
UIView *v = self.view;
do
{
v.clipsToBounds = NO;
v = v.superview;
}
while(v != nil);
Be aware that this will turn of clipping for all view in the hierarchy! - This might be more than you asked for:-).

Piemenu for WPF

Are there any working piemenu controls for WPF?
I've found this in my favorite , you can take a look at :
This
have a nice day.
This question is probably long dead, but just a note that the control Thomas M posted, while awesome, has a major issue: You need to mouse over and click on the actual item instead of the pie slice. This means that the pie slices are not completely adjacent and IMO defeats a lot of the clickability (Frits's law) advantages of the control. So while it looks like a pie menu, it really just positions everything radially.
I ended up doing this:
private static Path makeDeliciousKeyLimePieSlice(double innerRadius, double outerRadius,
double startAngle, double endAngle, Vector ofs)
{
Point p1 = new Point(Math.Cos(endAngle) * innerRadius, Math.Sin(endAngle) * innerRadius) + ofs;
Point p2 = new Point(Math.Cos(startAngle) * innerRadius, Math.Sin(startAngle) * innerRadius) + ofs;
Point p3 = new Point(Math.Cos(startAngle) * outerRadius, Math.Sin(startAngle) * outerRadius) + ofs;
Point p4 = new Point(Math.Cos(endAngle) * outerRadius, Math.Sin(endAngle) * outerRadius) + ofs;
PathFigure fig = new PathFigure(p1, new PathSegment[] {
new ArcSegment(p2, new Size(innerRadius, innerRadius), endAngle - startAngle, false, SweepDirection.Counterclockwise, true),
new LineSegment(p3, true),
new ArcSegment(p4, new Size(outerRadius, outerRadius), startAngle - endAngle, false, SweepDirection.Clockwise, true),
}, true).GetAsFrozen();
return new Path { Data = new PathGeometry(new[] { fig }).GetAsFrozen() };
}
This will create a "slice" of the pie. You can style this how you want if you want a true pie menu. Another option is to make it transparent (set the fill to Brushes.Transparent; it must have a fill to be hit-test visible), which looks good for radial context menus. Here's my WIP after about half an hour's work (I know the spacing sucks):
alt text http://public.blu.livefilestore.com/y1pdW5ibqWquKGosMSch9C5KmOTKkiZ35mAI7iFKKUKf3cm7TGSquXhO8hkkL9Ln6Z3tKn74u67C27Qb_AIWQxzhg/radial.png?psid=1
EDIT: ah; the cursor doesn't appear in the shot -- basically, if you use the path overlay, you can have the mouse outside the actual control but still have it highlighted.
This control needs a bit of work still but it's a great starting point and supports multiple levels of items. (ie: a hierarchy) Check it out here

Resources