How do you create an off-center PerspectiveCamera in WPF? - wpf

I'm trying to more or less recreate Johnny Lee's Wii head tracking app, but using an augmented reality toolkit for the tracking, and WPF for graphics. To do this, I need to create a perspective camera using the top, bottom, right, and left parameters to create my viewing frustum, instead of field of view and aspect ratio (to those familiar with OpenGL, I want to use the WPF equivalent of glFrustum instead of gluPerspective)
The problem is, those options don't seem to be available on WPF's PerspectiveCamera class. I could probably create the projection matrix manually if I had to and use MatrixCamera, but I'd like to avoid that. Does anyone know of a better way to do this?

I never did find a built-in way to do this, so I wrote my own. The math behind it can be found in the OpenGL glFrustum docs. If anyone else ever runs into this problem, this should work for you:
public Matrix3D CreateFrustumMatrix(double left, double right, double bottom, double top, double near, double far)
{
var a = (right + left) / (right - left);
var b = (top + bottom) / (top - bottom);
var c = -(far + near) / (far - near);
var d = -2 * far * near / (far - near);
return new Matrix3D(
2 * near / (right - left), 0, 0, 0,
0, 2 * near / (top - bottom), 0, 0,
a, b, c, -1,
0, 0, d, 0);
}
Just set MatrixCamera.ProjectionMatrix to the return value of that method, and you're all set.

Related

Implementing stroke drawing similar to InkCanvas

My problem effectively boils down to accurate mouse movement detection.
I need to create my own implementation of an InkCanvas and have succeeded for the most part, except for drawing strokes accurately.
void OnMouseMove(object sneder, MouseEventArgs e)
{
var position = e.GetPosition(this);
if (!Rect.Contains(position))
return;
var ratio = new Point(Width / PixelDisplay.Size.X, Height / PixelDisplay.Size.Y);
var intPosition = new IntVector(Math2.FloorToInt(position.X / ratio.X), Math2.FloorToInt(position.Y / ratio.Y));
DrawBrush.Draw(intPosition, PixelDisplay);
UpdateStroke(intPosition); // calls CaptureMouse
}
This works. The Bitmap (PixelDisplay) is updated and all is well. However, any kind of quick mouse movement causes large skips in the drawing. I've narrowed down the problem to e.GetPosition(this), which blocks the event long enough to be inaccurate.
There's this question which is long beyond revival, and its answers are unclear or simply don't have a noticeable difference.
After some more testing, the stated solution and similar ideas fail specifically because of e.GetPosition.
I know InkCanvas uses similar methods after looking through the source; detect the device, if it's a mouse, get its position and capture. I see no reason for the same process to not work identically here.
I ended up being able to partially solve this.
var position = e.GetPosition(this);
if (!Rect.Contains(position))
return;
if (DrawBrush == null)
return;
var ratio = new Point(Width / PixelDisplay.Size.X, Height / PixelDisplay.Size.Y);
var intPosition = new IntVector(Math2.FloorToInt(position.X / ratio.X), Math2.FloorToInt(position.Y / ratio.Y));
// Calculate pixel coordinates based on the control height
var lastPoint = CurrentStroke?.Points.LastOrDefault(new IntVector(-1, -1));
// Uses System.Linq to grab the last stroke, if it exists
PixelDisplay.Lock();
// My special locking mechanism, effectively wraps Bitmap.Lock
if (lastPoint != new IntVector(-1, -1)) // Determine if we're in the middle of a stroke
{
var alphaAdd = 1d / new IntVector(intPosition.X - lastPoint.Value.X, intPosition.Y - lastPoint.Value.Y).Magnitude;
// For some interpolation, calculate 1 / distance (magnitude) of the two points.
// Magnitude formula: Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2));
var alpha = 0d;
var xDiff = intPosition.X - lastPoint.Value.X;
var yDiff = intPosition.Y - lastPoint.Value.Y;
while (alpha < 1d)
{
alpha += alphaAdd;
var adjusted = new IntVector(
Math2.FloorToInt((position.X + (xDiff * alpha)) / ratio.X),
Math2.FloorToInt((position.Y + (yDiff * alpha)) / ratio.Y));
// Inch our way towards the current intPosition
DrawBrush.Draw(adjusted, PixelDisplay); // Draw to the bitmap
UpdateStroke(intPosition);
}
}
DrawBrush.Draw(intPosition, PixelDisplay); // Draw the original point
UpdateStroke(intPosition);
PixelDisplay.Unlock();
This implementation interpolates between the last point and the current one to fill in any gaps. It's not perfect when using a very small brush size for example, but is a solution nonetheless.
Some remarks
IntVector is a lazily implemented Vector2 by me, just using integers instead.
Math2 is a helper class. FloorToInt is short for (int)MathF.Round(...))

Processing doesn't draw complete curves using while loop

I'm investigating possibilities that Processing gives regarding generative art, and I stumbled upon a problem:
I'd like to generate multiple Bezier curves using a while loop. However, the program skips parts of some curves, while others are drawn properly.
Here's a working example:
void setup() {
size(1000,500);
background(#ffffff);
}
float[] i_x = {1,1};
float[] i_y = {1,1};
void draw() {
while (i_y[0] < height)
{
bezier(0,i_y[0],100,height-100,width - 100,height-100,width, i_y[0]);
i_y[0] = i_y[0] * 1.1;
}
save("bezier.jpg");
}
And here is the output. As you can see, only few of the curves are drawn in their full shape.
Also, when I draw one of the 'broken' curves out of the loop, it works fine.
I'd appreciate any help. I'm having good time learning coding concepts with visual output that Processing provides.
It works as intended. Look what happens when you change the background color (great post btw, the working example made it good enough for me to want to debug it!):
If you're clever, you'll notice that the "inside" of the curve has a color. Except that for now it's white. That's why only the topmost are "invisible": you're drawing them one after the other, starting topmost, so every new curve eats the last one by painting over it, but only "inside the curve". See what happens when I apply some color to differentiate the fill and the background better:
Now that the problem is obvious, here's the answer: transparency.
while (y < height)
{
fill(0, 0, 0, 0); // this is the important line, you can keep your algo for the rest
bezier(0, y, offset, height-offset, width - offset, height-offset, width, y);
y *= 1.1;
}
Which gives us this result:
Have fun!

Get texture coordinates of mouse position in SDL2?

I have the strict requirement to have a texture with resolution (let's say) of 512x512, always (even if the window is bigger, and SDL basically scales the texture for me, on rendering). This is because it's an emulator of a classic old computer assuming a fixed texture, I can't rewrite the code to adopt multiple texture sizes and/or texture ratios dynamically.
I use SDL_RenderSetLogicalSize() for the purpose I've described above.
Surely, when this is rendered into a window, I can get the mouse coordinates (window relative), and I can "scale" back to the texture position with getting the real window size (since the window can be resized).
However, there is a big problem here. As soon as window width:height ratio is not the same as the texture's ratio (for example in full screen mode, since the ratio of modern displays would not match of the ratio I want to use), there are "black bars" at the sides or top/bottom. Which is nice, since I want always the same texture ratio, fixed, and SDL does it for me, etc. However I cannot find a way to ask SDL where is my texture rendered exactly inside the window based on the fixed ratio I forced. Since I need the position within the texture only, and the exact texture origin is placed by SDL itself, not by me.
Surely, I can write some code to figure out how those "black bars" would change the origin of the texture, but I can hope there is a more simple and elegant way to "ask" SDL about this, since surely it has the code to position my texture somewhere, so I can re-use that information.
My very ugly (can be optimized, and floating point math can be avoided I think, but as the first try ...) solution:
static void get_mouse_texture_coords ( int x, int y )
{
int win_x_size, win_y_size;
SDL_GetWindowSize(sdl_win, &win_x_size, &win_y_size);
// I don't know if there is more sane way for this ...
// But we must figure out where is the texture within the window,
// which can be changed since the fixed ratio versus the window ratio (especially in full screen mode)
double aspect_tex = (double)SCREEN_W / (double)SCREEN_H;
double aspect_win = (double)win_x_size / (double)win_y_size;
if (aspect_win >= aspect_tex) {
// side ratio correction bars must be taken account
double zoom_factor = (double)win_y_size / (double)SCREEN_H;
int bar_size = win_x_size - (int)((double)SCREEN_W * zoom_factor);
mouse_x = (x - bar_size / 2) / zoom_factor;
mouse_y = y / zoom_factor;
} else {
// top-bottom ratio correction bars must be taken account
double zoom_factor = (double)win_x_size / (double)SCREEN_W;
int bar_size = win_y_size - (int)((double)SCREEN_H * zoom_factor);
mouse_x = x / zoom_factor;
mouse_y = (y - bar_size / 2) / zoom_factor;
}
}
Where SCREEN_W and SCREEN_H are the dimensions of the my texture, quite misleading by names, but anyway. Input parameters x and y are the window-relative mouse position (reported by SDL). mouse_x and mouse_y are the result, the texture based coordinates. This seems to work nicely. However, is there any sane solution or a better one?
The code which calls the function above is in my event handler loop (which I call regularly, of course), something like this:
void handle_sdl_events ( void ) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_MOUSEMOTION:
get_mouse_texture_coords(event.motion.x, event.motion.y);
break;
[...]

OpenGL - maintaining aspect ratio upon window resize

I am drawing a polygon in a square window. When I resize the window, for instance by fullscreening, the aspect ratio is disturbed. From a reference I found one way of preserving the aspect ratio. Here is the code:
void reshape (int width, int height) {
float cx, halfWidth = width*0.5f;
float aspect = (float)width/(float)height;
glViewport (0, 0, (GLsizei) width, (GLsizei) height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum(cx-halfWidth*aspect, cx+halfWidth*aspect, bottom, top, zNear, zFar);
glMatrixMode (GL_MODELVIEW);
}
Here, cx is the eye space center of the zNear plane in X. I request if someone could please explain how could I calculate this. I believe this should be the average of the initial first two arguments to glFrustum(). Am I right? Any help will be greatly appreciated.
It looks like what you want to do is maintain the field of view or angle of view when the aspect ratio changes. See the section titled 9.085 How can I make a call to glFrustum() that matches my call to gluPerspective()? of the OpenGL FAQ for details on how to do that. Here's the short version:
fov*0.5 = arctan ((top-bottom)*0.5 / near)
top = tan(fov*0.5) * near
bottom = -top
left = aspect * bottom
right = aspect * top
See the link for details.
The first two arguments are the X coordinates of the left and right clipping planes in eye space. Unless you are doing off-axis tricks (for example, to display uncentered projections across multiple monitors), left and right should have the same magnitude and opposite sign. Which would make your cx variable zero.
If you are having trouble understanding glFrustrum, you can always use gluPerspective instead, which has a somewhat simplified interface.

Mapping a 3D rectangle to a 2D screen

I've searched SO but I just can't figure this out. The other questions didn't help or I didn't understand them.
The problem is, I have a bunch of points in a 3D image. The points are for a rectangle, which doesn't look like a rectangle from the 3d camera's view because of perspective. The task is to map the points from that rectangle to the screen. I've seen some ways which some call "quad to quad transformations" but most of them are for mapping a 2d quadrilateral to another one. But I've got the X, Y and Z coordinates of the rectangle in the real world so I'm looking for some easier ways. Does anyone know any practical algorithm or method of doing this?
If it helps, my 3d camera is actually a Kinect device with OpenNI and NITE middlewares, and I'm using WPF.
Thanks in advance.
edit:
I also found the 3d-projection page on Wikipedia that used angles and cosines but that seems to be a difficult way (finding angles in the 3d image) and I'm not sure if it's the real solution or not.
You might want to check out projection matrices
That's how any 3D rasterizer "flattens" 3D volumes on a 2D screen.
See this code to get the projection matrix for a given WPF camera:
private static Matrix3D GetProjectionMatrix(OrthographicCamera camera, double aspectRatio)
{
// This math is identical to what you find documented for
// D3DXMatrixOrthoRH with the exception that in WPF only
// the camera's width is specified. Height is calculated
// from width and the aspect ratio.
double w = camera.Width;
double h = w / aspectRatio;
double zn = camera.NearPlaneDistance;
double zf = camera.FarPlaneDistance;
double m33 = 1 / (zn - zf);
double m43 = zn * m33;
return new Matrix3D(
2 / w, 0, 0, 0,
0, 2 / h, 0, 0,
0, 0, m33, 0,
0, 0, m43, 1);
}
private static Matrix3D GetProjectionMatrix(PerspectiveCamera camera, double aspectRatio)
{
// This math is identical to what you find documented for
// D3DXMatrixPerspectiveFovRH with the exception that in
// WPF the camera's horizontal rather the vertical
// field-of-view is specified.
double hFoV = MathUtils.DegreesToRadians(camera.FieldOfView);
double zn = camera.NearPlaneDistance;
double zf = camera.FarPlaneDistance;
double xScale = 1 / Math.Tan(hFoV / 2);
double yScale = aspectRatio * xScale;
double m33 = (zf == double.PositiveInfinity) ? -1 : (zf / (zn - zf));
double m43 = zn * m33;
return new Matrix3D(
xScale, 0, 0, 0,
0, yScale, 0, 0,
0, 0, m33, -1,
0, 0, m43, 0);
}
/// <summary>
/// Computes the effective projection matrix for the given
/// camera.
/// </summary>
public static Matrix3D GetProjectionMatrix(Camera camera, double aspectRatio)
{
if (camera == null)
{
throw new ArgumentNullException("camera");
}
PerspectiveCamera perspectiveCamera = camera as PerspectiveCamera;
if (perspectiveCamera != null)
{
return GetProjectionMatrix(perspectiveCamera, aspectRatio);
}
OrthographicCamera orthographicCamera = camera as OrthographicCamera;
if (orthographicCamera != null)
{
return GetProjectionMatrix(orthographicCamera, aspectRatio);
}
MatrixCamera matrixCamera = camera as MatrixCamera;
if (matrixCamera != null)
{
return matrixCamera.ProjectionMatrix;
}
throw new ArgumentException(String.Format("Unsupported camera type '{0}'.", camera.GetType().FullName), "camera");
}
You could do a basic orthographic projection (I'm thinking in terms of raytracing, so this might not apply to what you're doing):
The code is quite intuitive:
for y in image.height:
for x in image.width:
ray = new Ray(x, 0, z, Vector(0, 1, 0)) # Pointing forward
intersection = prism.intersection(ray) # Since you aren't shading, you can check only for intersections.
image.setPixel(x, y, intersection) # Returns black and white image of prism mapped to plane
You just shoot vectors with a direction of (0, 1, 0) directly out into space and record which ones hit.
I found this. Uses straight forward mathematics instead of matricies.
This is called perspective projection to convert from a 3D vertex to a 2D screen vertex. I used this to help me with my 3D program I have made.
HorizontalFactor = ScreenWidth / Tan(PI / 4)
VerticalFactor = ScreenHeight / Tan(PI / 4)
ScreenX = ((X * HorizontalFactor) / Y) + HalfWidth
ScreenY = ((Z * VerticalFactor) / Y) + HalfHeight
Hope this could help. I think its what you where looking for. Sorry about the formatting (new here)
Mapping points in a 3d world to a 2d screen is part of the job of frameworks like OpenGL and Direct3d. It's called Rasterisation like Heandel said. Perhaps you could use Direct3d?

Resources