I'm trying to set the camera to move around the rendered object and I'm following this guide:
MSDN Guide on Rotating and Moving The Camera
here's the code I got:
public partial class MainPage : UserControl
{
float _cameraRotationRadians = 0.0f;
Vector3 _cameraPosition = new Vector3(5.0f, 5.0f, 5.0f);
List<TexturedMesh> _meshes;
BasicEffect _effect;
public MainPage()
{
InitializeComponent();
}
private void DrawingSurface_Loaded(object sender, RoutedEventArgs e)
{
GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
RenderModeReason reason = GraphicsDeviceManager.Current.RenderModeReason;
_meshes = StreamHelper.ToMesh(device, "capsule.obj");
_effect = new BasicEffect(GraphicsDeviceManager.Current.GraphicsDevice);
_effect.TextureEnabled = false;
_effect.World = Matrix.Identity;
_effect.View = Matrix.CreateLookAt(_cameraPosition, new Vector3(0.0f, 0.0f, 0.0f), Vector3.Up);
_effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 1.667f, 1.0f, 100.0f);
}
private void DrawingSurface_Draw(object sender, DrawEventArgs e)
{
GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, new Microsoft.Xna.Framework.Color(0, 0, 0, 0), 10.0f, 0);
device.RasterizerState = new RasterizerState()
{
CullMode = CullMode.None
};
foreach (TexturedMesh mesh in _meshes)
{
// Load current vertex buffer
device.SetVertexBuffer(mesh.VertexBuffer);
// Apply texture
if (mesh.Texture != null)
{
_effect.Texture = mesh.Texture;
_effect.TextureEnabled = true;
}
else
_effect.TextureEnabled = false;
// Draw the mesh
foreach (EffectPass pass in _effect.CurrentTechnique.Passes)
{
pass.Apply();
device.SamplerStates[0] = SamplerState.LinearClamp;
device.DrawPrimitives(PrimitiveType.TriangleList, 0, mesh.VertexBuffer.VertexCount / 3);
}
// updates camera position
UpdateCameraPosition();
}
e.InvalidateSurface();
}
private void UpdateCameraPosition()
{
float nearClip = 1.0f;
float farClip = 2000.0f;
// set object origin
Vector3 objectPosition = new Vector3(0, 0, 0);
// set angle to rotate to
float cameraDegree = _cameraRotationRadians;
Matrix rotationMatrix = Matrix.CreateRotationX(MathHelper.ToRadians(cameraDegree));
// set where camera is looking at
Vector3 transformedReference = Vector3.Transform(objectPosition, rotationMatrix);
Vector3 cameraLookAt = _cameraPosition + transformedReference;
// set view matrix and projection matrix
_effect.View = Matrix.CreateLookAt(_cameraPosition, objectPosition, Vector3.Up);
_effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 1.667f, nearClip, farClip);
}
private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// slider.Value;
_cameraRotationRadians += (float)slider.Value;
}
}
I have a slider control that gives the radians value. When the slider value changes, I then set a new angle for the camera. The UpdateCameraPosition() is called every time the DrawingSurface refreshes after the objects are loaded.
Now when I try to move the slider, the position of the camera never changes. Can someone help and tell me what to fix to make it revolve around the whole group of objects?
You are basically trying to rotate the vector (0,0,0). Rotating a vector in the origo will have no result.
You should instead rotate the camera-position.
That guide is how to rotate the camera in place, but it sounds like you are trying to have your camera orbit your object. In that case, both transformedReference and cameraLookAt are not needed.
Simply remove those two lines and rotate the _cameraPosition around the objectPosition to have the camera orbit the object.
//add this line in place of those two lines:
_cameraPosition = Vector3.Transform(_cameraPosition, rotationMatrix);
//now when you plug _cameraPosition into Matrix.CreateLookAt(), it will offer it a new camera position
Technically my snippet causes _cameraPosition to orbit the world origin 0,0,0. It works because your objectPosition happens to be at the world origin so the camera is orbiting objectPosition too.
But if objectPosition ever wanders away from the origin, you can still cause the camera to orbit the object by translating the camera/object system to the origin, perform the rotation, the translate them back. it is easier that it sounds:
_cameraPosition = Vector3.Transform(_cameraPosition - objectPosition, rotationMatrix) + objectPosition;
//now the camera will orbit the object no matter where in the world the object is.
Related
I have problem with GDI. I do it in WinForms. There is what I got:
And there is my code:
Graphics phantom = this.pictureBox1.CreateGraphics();
Pen blackPen = new Pen(Color.Black, 3);
Rectangle rect = new Rectangle(0, 0, 200, 150);
float startAngle = 180F;
float sweepAngle = 180F;
phantom.DrawArc(blackPen, rect, startAngle, sweepAngle);
phantom.Dispose();
I want to get something like that:
Really sorry for my paint skills. Is it possible to create such a thing from the arc itself or do I have to do it from an ellipse? I don't know how to go about it. Any tips are welcome. Thanks.
From my comments on the original post:
You have two circles, let's call them lower and upper. Define the
upper circle as a GraphicsPath and pass that to the constructor of a
Region. Now pass that Region to e.Graphics via the ExcludeClip method.
Now draw the lower circle, which will be missing the top part because
of the clipping. Next, Reset() the Graphics and define the lower
circle in a GraphicsPath. Use Graphics.Clip() this time, and chase
that with drawing the upper circle. It will only be visible where the
lower circle clip was.
Proof of concept:
Code:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics phantom = e.Graphics;
using (Pen blackPen = new Pen(Color.Black, 3))
{
Rectangle upper = new Rectangle(-50, -250, 300, 300);
GraphicsPath upperGP = new GraphicsPath();
upperGP.AddEllipse(upper);
using (Region upperRgn = new Region(upperGP))
{
Rectangle lower = new Rectangle(0, 0, 200, 150);
GraphicsPath lowerGP = new GraphicsPath();
lowerGP.AddEllipse(lower);
float startAngle = 180F;
float sweepAngle = 180F;
phantom.ExcludeClip(upperRgn);
phantom.DrawArc(blackPen, lower, startAngle, sweepAngle);
phantom.ResetClip();
phantom.SetClip(lowerGP);
phantom.DrawEllipse(blackPen, upper);
}
}
}
I have an image and i am panning it after zooming, on panning horizontally, image gets moved leaving white space at the left. I want to restrict when the image left bounds has reached. Same thing for top, right, bottom panning.
Panned image
Expected output
private void Grid1_MouseMove(object sender, MouseEventArgs e)
{
if (!image1.IsMouseCaptured) return;
Point p = e.MouseDevice.GetPosition(image1);
var rect2 = new Rect(image1.RenderSize);
Bounds = image1.TransformToAncestor(grid1).TransformBounds(rect2);
matrix = zoomMatrixTransform.Matrix;
Vector v = start - e.GetPosition(grid1);
matrix.OffsetX = origin.X - v.X;
matrix.OffsetY = origin.Y - v.Y;
zoomMatrixTransform.Matrix = matrix;
image1.RenderTransformOrigin = new Point(0.5, 0.5);
image1.LayoutTransform = zoomMatrixTransform;
}
I have attached the panned image and i want to restrict the red highlighted area. Also attached the expected output.
I'm generating a bunch of RectangleF objects having different sizes and positions. What would be the best way to fill them with a gradient Brush in GDI+?
In WPF I could create a LinearGradientBrush, set Start and End relative points and WPF would take care of the rest.
In GDI+ however, the gradient brush constructor requires the position in absolute coordinates, which means I have to create a Brush for each of the rectangle, which would be a very complex operation.
Am I missing something or that's indeed the only way?
You can specify a transform at the moment just before the gradient is applied if you would like to declare the brush only once. Note that using transformations will override many of the constructor arguments that can be specified on a LinearGradientBrush.
LinearGradientBrush.Transform Property (System.Drawing.Drawing2D)
To modify the transformation, call the methods on the brush object corresponding to the desired matrix operations. Note that matrix operations are not commutative, so order is important. For your purposes, you'll probably want to do them in this order for each rendition of your rectangles: Scale, Rotate, Offset/Translate.
LinearGradientBrush.ResetTransform Method # MSDN
LinearGradientBrush.ScaleTransform Method (Single, Single, MatrixOrder) # MSDN
LinearGradientBrush.RotateTransform Method (Single, MatrixOrder) # MSDN
LinearGradientBrush.TranslateTransform Method (Single, Single, MatrixOrder) # MSDN
Note that the system-level drawing tools don't actually contain a stock definition for gradient brush, so if you have performance concerns about making multiple brushes, creating a multitude of gradient brushes shouldn't cost any more than the overhead of GDI+/System.Drawing maintaining the data required to define the gradient and styling. You may be just as well off to create a Brush per rectangle as needed, without having to dive into the math required to customize the brush via transform.
Brush Functions (Windows) # MSDN
Here is a code example you can test in a WinForms app. This app paints tiles with a gradient brush using a 45 degree gradient, scaled to the largest dimension of the tile (naively calculated). If you fiddle with the values and transformations, you may find that it isn't worth using the technique setting a transform for all of your rectangles if you have non-trivial gradient definitions. Otherwise, remember that your transformations are applied at the world-level, and in the GDI world, the y-axis is upside down, whereas in the cartesian math world, it is ordered bottom-to-top. This also causes the angle to be applied clockwise, whereas in trigonometry, the angle progresses counter-clockwise in increasing value for a y-axis pointing up.
using System.Drawing.Drawing2D;
namespace TestMapTransform
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle rBrush = new Rectangle(0,0,1,1);
Color startColor = Color.DarkRed;
Color endColor = Color.White;
LinearGradientBrush br = new LinearGradientBrush(rBrush, startColor, endColor, LinearGradientMode.Horizontal);
int wPartitions = 5;
int hPartitions = 5;
int w = this.ClientSize.Width;
w = w - (w % wPartitions) + wPartitions;
int h = this.ClientSize.Height;
h = h - (h % hPartitions) + hPartitions;
for (int hStep = 0; hStep < hPartitions; hStep++)
{
int hUnit = h / hPartitions;
for (int wStep = 0; wStep < wPartitions; wStep++)
{
int wUnit = w / wPartitions;
Rectangle rTile = new Rectangle(wUnit * wStep, hUnit * hStep, wUnit, hUnit);
if (e.ClipRectangle.IntersectsWith(rTile))
{
int maxUnit = wUnit > hUnit ? wUnit : hUnit;
br.ResetTransform();
br.ScaleTransform((float)maxUnit * (float)Math.Sqrt(2d), (float)maxUnit * (float)Math.Sqrt(2d), MatrixOrder.Append);
br.RotateTransform(45f, MatrixOrder.Append);
br.TranslateTransform(wUnit * wStep, hUnit * hStep, MatrixOrder.Append);
e.Graphics.FillRectangle(br, rTile);
br.ResetTransform();
}
}
}
}
private void Form1_Resize(object sender, EventArgs e)
{
this.Invalidate();
}
}
}
Here's a snapshot of the output:
I recommend you to create a generic method like this:
public void Paint_rectangle(object sender, PaintEventArgs e)
{
RectangleF r = new RectangleF(0, 0, e.ClipRectangle.Width, e.ClipRectangle.Height);
if (r.Width > 0 && r.Height > 0)
{
Color c1 = Color.LightBlue;
Color c2 = Color.White;
Color c3 = Color.LightBlue;
LinearGradientBrush br = new LinearGradientBrush(r, c1, c3, 90, true);
ColorBlend cb = new ColorBlend();
cb.Positions = new[] { 0, (float)0.5, 1 };
cb.Colors = new[] { c1, c2, c3 };
br.InterpolationColors = cb;
// paint
e.Graphics.FillRectangle(br, r);
}
}
then, for every rectangle just call:
yourrectangleF.Paint += new PaintEventHandler(Paint_rectangle);
If the gradrients colors are all the same, you can make that method shorter. Hope that helped..
I want to draw like in the old qbasik, where you can into 5 lines and PSET (x, y) derive any graph, or Lissajous figures.
Question: what better way to go for WPF? and way for XNA?
Any samples?
For WPF and Silverlight
WriteableBitmap
http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.writeablebitmap.aspx
WriteableBitmapEx library. Tries to compensate that with extensions methods that are easy to use like built in methods and offer GDI+ like functionality:
http://writeablebitmapex.codeplex.com/
In XNA this isn't the most efficient thing in general, but I think your best bet is to probably create a texture and set each pixel using SetData, and render it to the screen with SpriteBatch.
SpriteBatch spriteBatch;
Texture2D t;
Color[] blankScreen;
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
//initialize texture
t = new Texture2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, false, SurfaceFormat.Color);
//clear screen initially
blankScreen = new Color[GraphicsDevice.Viewport.Width * GraphicsDevice.Viewport.Height];
for (int i = 0; i < blankScreen.Length; i++)
{
blankScreen[i] = Color.Black;
}
ClearScreen();
}
private void Set(int x, int y, Color c)
{
Color[] cArray = { c };
//unset texture from device
GraphicsDevice.Textures[0] = null;
t.SetData<Color>(0, new Rectangle(x, y, 1, 1), cArray, 0, 1);
//reset
GraphicsDevice.Textures[0] = t;
}
private void ClearScreen()
{
//unset texture from device
GraphicsDevice.Textures[0] = null;
t.SetData<Color>(blankScreen);
//reset
GraphicsDevice.Textures[0] = t;
}
protected override void Draw(GameTime gameTime)
{
spriteBatch.Begin();
spriteBatch.Draw(t, Vector2.Zero, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
With this you can call either Set or ClearScreen at will in your Update or Draw. You may have to play with the texture index (I just used 0 for this example, might not be it for you), and also you only need to unset / reset one time per frame, so you can optimize that depending on how you use them.
I Have a canvas full of objects that I zoom and pan using
this.source = VisualTreeHelper.GetChild(this, 0) as FrameworkElement;
this.zoomTransform = new ScaleTransform();
this.transformGroup = new TransformGroup();
this.transformGroup.Children.Add(this.zoomTransform);
this.transformGroup.Children.Add(this.translateTransform);
this.source.RenderTransform = this.transformGroup;
I then have a method that moves the canvas to a a certain point (in the original coordinates) to the center of the screen:
public void MoveTo(Point p)
{
var parent= VisualTreeHelper.GetParent(this) as FrameworkElement;
Point centerPoint = new Point(parent.ActualWidth / 2, parent.ActualHeight / 2);
double x = centerPoint.X - p.X;
double y = centerPoint.Y - p.Y;
x *= this.zoomTransform.ScaleX;
y *= this.zoomTransform.ScaleY;
this.translateTransform.BeginAnimation(TranslateTransform.XProperty, CreatePanAnimation(x), HandoffBehavior.Compose);
this.translateTransform.BeginAnimation(TranslateTransform.YProperty, CreatePanAnimation(y), HandoffBehavior.Compose);
}
private DoubleAnimation CreatePanAnimation(double toValue)
{
var da = new DoubleAnimation(toValue, new Duration(TimeSpan.FromMilliseconds(300)));
da.AccelerationRatio = 0.1;
da.DecelerationRatio = 0.9;
da.FillBehavior = FillBehavior.HoldEnd;
da.Freeze();
return da;
}
Everything works great until I actually have a zoom animation active after which the pan animation is inaccurate. I've tried different ways of calculation x,y and the centerpoint but can't seem to get it right. Any help appreciated, should be simple :)
I'd also like to make a method that both animates zooming and pans to a point, a little unsure on the ordering to accomplish that
Nevermind, I'm stupid
Point centerPoint = new Point(parent.ActualWidth / 2 / this.zoomTransform.ScaleX, parent.ActualHeight / 2 / this.zoomTransform.ScaleY);
I am still interested in how I can combine the scale and zoom animations though
this.translateTransform.BeginAnimation(TranslateTransform.XProperty, CreatePanAnimation(x), HandoffBehavior.Compose);
this.translateTransform.BeginAnimation(TranslateTransform.YProperty, CreatePanAnimation(y), HandoffBehavior.Compose);
this.zoomTransform.BeginAnimation(ScaleTransform.ScaleXProperty, CreateZoomAnimation(factor));
this.zoomTransform.BeginAnimation(ScaleTransform.ScaleYProperty, CreateZoomAnimation(factor));
wont work since the scale and pan values wont be synced...