I am trying to write an algorithm to convert my mouse click to 3D coordinates (to insert an object at this point).
I have "ground" level where Y = 0 and I want to calculate X and Z based on my mouse click. My function currently looks like that:
Point p = this.control.PointToClient(new Point(System.Windows.Forms.Cursor.Position.X, System.Windows.Forms.Cursor.Position.Y));
Vector3 pos = GeometryHelper.Unproject(new Vector3(p.X, 0f, p.Y), viewport.X, viewport.Y, viewport.Width, viewport.Height, projectionPlane.Near, projectionPlane.Far, Matrix4.Invert(mProjectionMatrix * camera.GetViewMatrix()));
active.applyGeometry(pos);
function applyGeometry simply sets the position of an object. I believe passed arguments are self-explanatory.
My Unproject function looks this way:
public static Vector3 Unproject(Vector3 vector, float x, float y, float width, float height, float minZ, float maxZ, Matrix4 inverseWorldViewProjection)
{
Vector4 result;
result.X = ((((vector.X - x) / width) * 2.0f) - 1.0f);
result.Y = ((((vector.Y - y) / height) * 2.0f) - 1.0f);
result.Z = (((vector.Z / (maxZ - minZ)) * 2.0f) - 1.0f);
result.X =
result.X * inverseWorldViewProjection.M11 +
result.Y * inverseWorldViewProjection.M21 +
result.Z * inverseWorldViewProjection.M31 +
inverseWorldViewProjection.M41;
result.Y =
result.X * inverseWorldViewProjection.M12 +
result.Y * inverseWorldViewProjection.M22 +
result.Z * inverseWorldViewProjection.M32 +
inverseWorldViewProjection.M42;
result.Z =
result.X * inverseWorldViewProjection.M13 +
result.Y * inverseWorldViewProjection.M23 +
result.Z * inverseWorldViewProjection.M33 +
inverseWorldViewProjection.M43;
result.W =
result.X * inverseWorldViewProjection.M14 +
result.Y * inverseWorldViewProjection.M24 +
result.Z * inverseWorldViewProjection.M34 +
inverseWorldViewProjection.M44;
result /= result.W;
return new Vector3(result.X, result.Y, result.Z);
}
The problem is that Unproject function returns result close to 0,0,0 and that makes my object appear around 0,0,0. Any idea how to modify it to work properly?
Update
I believe I have given enough details on this case, but in case you'd need something else to help me out just do not hesitate to tell me ; )
Related
I am currently creating a rubiks cube project. The cube solves, but now I'm trying to implement a 3d model of this cube.
At the moment the x axis and z axis rotations work correctly, but the y axis rotation seems to start of as a cube but as it rotates round becomes more of a trapezium as it rotates 180'.
I have this code:
Point3D final;
double x = rotation.x;
final.x = original.x;
final.y = original.y * Math.Cos(x) - original.z * Math.Sin(x);
final.z = original.y * Math.Sin(x) + original.z * Math.Cos(x);
original.x = final.x;
original.y = final.y;
original.z = final.z;
x = rotation.y;
final.x = original.z * Math.Sin(x) + original.x * Math.Cos(x);
final.y = original.y;
final.z = original.y * Math.Cos(x) - original.x * Math.Sin(x);
original.x = final.x;
original.y = final.y;
original.z = final.z;
x = rotation.z;
final.x = original.x * Math.Cos(x) - original.y * Math.Sin(x);
final.y = original.x * Math.Sin(x) + original.y * Math.Cos(x);
final.z = original.z;
typo. Change line for y-rotation to
final.z = original.z * Math.Cos(x) - original.x * Math.Sin(x);
You were using original.y instead of original.z, but for a y-rotation the value of y does not play into the rotation.
May I suggest you define the rotations in methods
public static class Rotations
{
public static Point3D RotateAboutX(this Point3D point, double angle)
{
return new Point3D(
point.X,
Math.Cos(angle) * point.Y- Math.Sin(angle) * point.Z,
Math.Sin(angle) * point.Y+ Math.Cos(angle) * point.Z);
}
public static Point3D RotateAboutY(this Point3D point, double angle)
{
return new Point3D(
Math.Cos(angle) * point.X + Math.Sin(angle) * point.Z,
point.Y,
-Math.Sin(angle) * point.X + Math.Cos(angle) * point.Z);
}
public static Point3D RotateAboutZ(this Point3D point, double angle)
{
return new Point3D(
Math.Cos(angle) * point.X - Math.Sin(angle) * point.Y,
Math.Sin(angle) * point.X + Math.Cos(angle) * point.Y,
point.Z);
}
}
and then used them as needed. For Example
Point3D final = original.RotateAboutX(rotation.x)
.RotateAboutY(rotation.y)
.RotateAboutZ(rotation.z);
or the remain true to the original code
Point3D final = original.RotateAboutX(rotation.x);
original = final;
final = original.RotateAboutY(rotation.y);
original = final;
final = original.RotateAboutZ(rotation.z);
I have managed to find code that determines if a Line intersects with a rectangle. My problem is when the Rectangle is rotated. I have looked hi and low to find code that will give me the coordinates of the corners of the rectangle after the transform is performed with no luck. My rectangle when it is rotated is rotated around CenterX and CenterY of 0,0.
I'd appreciate any code that you may have that does this.
Thanks!
More information:
I am working on a program that I want to be able to select one or more rectangles on a canvas by drawing a line. The rectangles can be rotated.
I have tried the following code. The second function works properly for non rotated rectangles but not for rotated rectangles.:
public bool AdjustedIntersects(FrameworkElement elem, Line line)
{
double x = Canvas.GetLeft(elem);
double y = Canvas.GetTop(elem);
double X = x * Math.Cos(((RotateTransform)elem.RenderTransform).Angle)
- y * Math.Sin(((RotateTransform)elem.RenderTransform).Angle);
double Y = x * Math.Sin(((RotateTransform)elem.RenderTransform).Angle)
+ y * Math.Cos(((RotateTransform)elem.RenderTransform).Angle);
x = Canvas.GetLeft(elem) + elem.Width;
y = Canvas.GetTop(elem) + elem.Height;
double X2 = x * Math.Cos(((RotateTransform)elem.RenderTransform).Angle)
- y * Math.Sin(((RotateTransform)elem.RenderTransform).Angle);
double Y2 = x * Math.Sin(((RotateTransform)elem.RenderTransform).Angle)
+ y * Math.Cos(((RotateTransform)elem.RenderTransform).Angle);
return SegmentIntersectRectangle(X, Y,X2,Y2,
line.X1, line.Y1, line.X2, line.Y2);
}
public bool SegmentIntersectRectangle(
double rectangleMinX,
double rectangleMinY,
double rectangleMaxX,
double rectangleMaxY,
double p1X,
double p1Y,
double p2X,
double p2Y)
{
// Find min and max X for the segment
double minX = p1X;
double maxX = p2X;
if (p1X > p2X)
{
minX = p2X;
maxX = p1X;
}
// Find the intersection of the segment's and rectangle's x-projections
if (maxX > rectangleMaxX)
{
maxX = rectangleMaxX;
}
if (minX < rectangleMinX)
{
minX = rectangleMinX;
}
if (minX > maxX) // If their projections do not intersect return false
{
return false;
}
// Find corresponding min and max Y for min and max X we found before
double minY = p1Y;
double maxY = p2Y;
double dx = p2X - p1X;
if (Math.Abs(dx) > 0.0000001)
{
double a = (p2Y - p1Y) / dx;
double b = p1Y - a * p1X;
minY = a * minX + b;
maxY = a * maxX + b;
}
if (minY > maxY)
{
double tmp = maxY;
maxY = minY;
minY = tmp;
}
// Find the intersection of the segment's and rectangle's y-projections
if (maxY > rectangleMaxY)
{
maxY = rectangleMaxY;
}
if (minY < rectangleMinY)
{
minY = rectangleMinY;
}
if (minY > maxY) // If Y-projections do not intersect return false
{
return false;
}
return true;
}
A polygon intersects with a line if one of lines of the poligon overlap with the line. Then try this:
Find four conners of the rectangle by the above way.
Check if one of lines make from sequence of conners overlap with the line. I have a ideal for it:
private bool IsStraightLineOverlap(System.Windows.Shapes.Line line1, Line line2)
{
var line1Min_X = Math.Min(line1.X1, line1.X2);
var line1Max_X = Math.Max(line1.X1, line1.X2);
var line1Min_Y = Math.Min(line1.Y1, line1.Y2);
var line1Max_Y = Math.Max(line1.Y1, line1.Y2);
var line2Min_X = Math.Min(line2.X1, line2.X2);
var line2Max_X = Math.Max(line2.X1, line2.X2);
var line2Min_Y = Math.Min(line2.Y1, line2.Y2);
var line2Max_Y = Math.Max(line2.Y1, line2.Y2);
var isOverlap_X = (line1Min_X <= line2Max_X && line1Max_X >= line2Min_X);
var isOverlap_Y = (line1Min_Y <= line2Max_Y && line1Max_Y >= line2Min_Y);
return isOverlap_X && isOverlap_Y;
}
In our game suite I have to find rotated points for various purposes.
Here's an extension method:
public static class PointExtension
{
public static Point GetRotatedPoint(this Point point, Point centerPoint, double angleInDegrees)
{
double angleInRadians = angleInDegrees * (Math.PI / 180.0);
double cosTheta = Math.Cos(angleInRadians);
double sinTheta = Math.Sin(angleInRadians);
return new Point
{
X =
(int)
(cosTheta * (point.X - centerPoint.X) -
sinTheta * (point.Y - centerPoint.Y) + centerPoint.X),
Y =
(int)
(sinTheta * (point.X - centerPoint.X) +
cosTheta * (point.Y - centerPoint.Y) + centerPoint.Y)
};
}
}
I also have to work out the cells on a grid that a line passes through.
To do this I use a bresenham line algorithm.
You can google this and find various implementations.
Here's mine:
public static IEnumerable<Point> GetOrderedPointsOnLine(int x0, int y0, int x1, int y1)
{
bool steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0);
if (steep)
{
Swap<int>(ref x0, ref y0);
Swap<int>(ref x1, ref y1);
}
int dx = Math.Abs(x1 - x0);
int dy = Math.Abs(y1 - y0);
int error = (dx / 2);
int ystep = (y0 < y1 ? 1 : -1);
int xstep = (x0 < x1 ? 1 : -1);
int y = y0;
for (int x = x0; x != (x1 + xstep); x += xstep)
{
yield return new Point((steep ? y : x), (steep ? x : y));
error = error - dy;
if (error < 0)
{
y += ystep;
error += dx;
}
}
yield break;
}
The outline of a rectangle is of course 4 lines.
There is an edge case where both an edge and the line can be a variation of 4 degrees and intersection exactly picks cells don't match.
To obviate that you could use a bresenham variation which picks both cells the line partially passes through.
Or you can build two geometries and see if you get anything overlaps when you use the wpf library to detect intersection.
I'm not sure what happens if you apply a transform to a geometry and then use geometry.fillcontainswithdetail
https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.geometry.fillcontainswithdetail?redirectedfrom=MSDN&view=net-5.0#overloads
There's also combinedgeometry to consider.
Pick the right options and it'll only give you what overlaps between 2 geometries.
I use neither because in game has to be very fast.
For pre game calculations I render offscreen and examine the bytes of the image I get. This is the fastest simplest way I've found to see which cells (px) an irregular shape occupies.
And another way you could approach this.
Solution 1: break the problem down into something that is easier to solve:
Pick any edge on the rectangle, represented by points P1 and P2.
Translate both the points in your rectangle and the points that define your line by -P1, so that the P1 point is now at the origin.
Calculate the angle of your edge i.e. Math.Atan2(deltaY, deltaX).
Rotate the points for both the rectangle and line in the opposite direction, so that you now have an axis-aligned rectangle.
Do your line/rectangle hit test between these new primitives, using the algorithm you already have for axis-aligned rectangles.
If you need the actual point of intersection in proper world space coordinates then rotate it forward by the angle and translate by +P1.
Solution 2: test the line against each line that forms the rectangle, your problem is now 4 line-to-line intersection tests.
Disclaimer: this is not c#. I only know javascript but the math should be the same.
The way I attack this is by creating an array for my shape that I use to store each vertex. I then use those points to determine where the objects boundaries are. I am assuming that translate and rotate work the same in c# and the objects coordinate are always axis-aligned.
When I draw my rectangle I draw it with the x (left) as -width/2 and y (top) as -height/2. This is because I am going to use translate to position it where I want it and also allow it to rotate from the center.
ctx.save();
ctx.translate(this.x, this.y)
ctx.rotate(this.rotation);
ctx.fillStyle = this.color;
ctx.fillRect(-this.width/2, -this.height/2, this.width, this.height)
ctx.restore();
I don't know if c# uses save and restore but it would be the same as just translating and rotating it back after i.e.
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.fillStyle = this.color;
ctx.fillRect(-this.width/2, -this.height/2, this.width, this.height);
ctx.rotate(-this.rotation);
ctx.translate(-this.x, -this.y)
Also you'll notice in the snippet code that I created an array to store my x and y coordinates of each vertex
this.vertices = [];
for (let i = 0; i < 4; i++) {
//initially set to (0, 0) and updated in updateVertices()
this.vertices.push({ x: 0, y: 0 });
}
This is part of my rectangle object by the array could be global also.
The part that really matters is the math associated to updating the position of the vertices. In this snippet I use a function called updateVertices(). In this function I need to first calculate the sin and cos based on the current rotation.
let cos = Math.cos(this.rotation); //passing in radians in JS
let sin = Math.sin(this.rotation); //passing in radians in JS
Once I have that I just update the array of vertices that I created
//update Top Left Corner
this.vertices[0].x =
(this.x - this.centerX) * cos -
(this.y - this.centerY) * sin +
(this.centerX - this.width / 2);
this.vertices[0].y =
(this.x - this.centerX) * sin +
(this.y - this.centerY) * cos +
(this.centerY - this.height / 2);
Do that with all four corners. The math is slightly different for each vertex.
That's it. You have an array with all 4 vertices and can use them how you want. In this example I iterate over them passing two (adjacent) at time to my intersectLines() function to see if my vector lines intersects. Since I am testing 4 edges I use a loop to test all four sides against my vector, I do this in my passToIntersectFunction() function.
In this snippet you can use the mouse to move the vector around and see how the intersect points move.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;
let ptX, ptY;
let intersectPoints = [];
let mouse = {
x: null,
y: null
};
canvas.addEventListener("mousemove", (e) => {
mouse.x = e.x - canvas.getBoundingClientRect().x;
mouse.y = e.y - canvas.getBoundingClientRect().y;
});
class Square {
constructor() {
this.x = 100;
this.y = 100;
this.width = 50;
this.height = 50;
this.centerX = this.x + this.width / 2;
this.centerY = this.y + this.height / 2;
this.color = "red";
this.angle = 0;
this.rotation = (this.angle * Math.PI) / 180; //convert to rads
//used to store all four vertices
this.vertices = [];
for (let i = 0; i < 4; i++) {
//initially set to (0, 0) and updated in updateVertices()
this.vertices.push({ x: 0, y: 0 });
}
}
draw() {
this.angle += 0.5;
this.rotation = (this.angle * Math.PI) / 180;
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.fillStyle = this.color;
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
ctx.rotate(-this.rotation);
ctx.translate(-this.x, -this.y);
}
drawIntersectPoint() {
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(ptX, ptY, 3, 0, Math.PI * 2);
ctx.fill();
}
drawVertices() {
ctx.fillStyle = "blue";
ctx.beginPath();
for (let i = 0; i < this.vertices.length; i++) {
ctx.arc(this.vertices[i].x, this.vertices[i].y, 3, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
updateVertices() {
let cos = Math.cos(this.rotation);
let sin = Math.sin(this.rotation);
//update Top Left Corner
this.vertices[0].x =
(this.x - this.centerX) * cos -
(this.y - this.centerY) * sin +
(this.centerX - this.width / 2);
this.vertices[0].y =
(this.x - this.centerX) * sin +
(this.y - this.centerY) * cos +
(this.centerY - this.height / 2);
//updates Top Right Corner
this.vertices[1].x =
(this.x + this.width - this.centerX) * cos -
(this.y - this.centerY) * sin +
(this.centerX - this.width / 2);
this.vertices[1].y =
(this.x + this.width - this.centerX) * sin +
(this.y - this.centerY) * cos +
(this.centerY - this.height / 2);
//updates Bottom Right Corner
this.vertices[2].x =
(this.x + this.width - this.centerX) * cos -
(this.y + this.height - this.centerY) * sin +
(this.centerX - this.width / 2);
this.vertices[2].y =
(this.x + this.width - this.centerX) * sin +
(this.y + this.height - this.centerY) * cos +
(this.centerY - this.height / 2);
//updates Bottom Left Corner
this.vertices[3].x =
(this.x - this.centerX) * cos -
(this.y + this.height - this.centerY) * sin +
(this.centerX - this.width / 2);
this.vertices[3].y =
(this.x - this.centerX) * sin +
(this.y + this.height - this.centerY) * cos +
(this.centerY - this.height / 2);
}
}
let square = new Square();
class Vector {
constructor() {
this.x1 = 200;
this.y1 = 100;
this.x2 = mouse.x;
this.y2 = mouse.y;
}
draw() {
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.moveTo(this.x1, this.y1);
ctx.lineTo(this.x2, this.y2);
ctx.stroke();
}
updateVector() {
this.x2 = mouse.x;
this.y2 = mouse.y;
this.draw();
}
}
let vector = new Vector();
function intersectLines(coord1, coord2, vector) {
//this if statement just keeps the array from constantly growing
if (intersectPoints.length > 2) {
intersectPoints.shift();
}
let x1 = coord1.x;
let x2 = coord2.x;
let y1 = coord1.y;
let y2 = coord2.y;
let x3 = vector.x1;
let x4 = vector.x2;
let y3 = vector.y1;
let y4 = vector.y2;
let d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (d == 0) {
return;
}
let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / d;
let u = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d;
if (t > 0 && t < 1 && u > 0) {
intersectPoints.push({ x: x1 + t * (x2 - x1), y: y1 + t * (y2 - y1) });
}
return;
}
function passToIntersectFunction() {
for (let i = 0; i < square.vertices.length; i++) {
intersectLines(
square.vertices[i],
square.vertices[i + 1] ?? square.vertices[0],
vector
);
}
}
function drawIntersectPoints() {
for (let i = 0; i < intersectPoints.length; i++) {
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(intersectPoints[i].x, intersectPoints[i].y, 3, 0, Math.PI * 2);
ctx.fill();
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
square.draw();
square.updateVertices();
square.drawIntersectPoint();
square.drawVertices();
vector.updateVector();
drawIntersectPoints();
passToIntersectFunction();
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>
Sorry its not c# but maybe it can help.
The .X3D format has an interesting rotation system. Unlike most formats containing rotation values around the X, Y and Z axis, .X3D gives a normalized direction vector and then gives a value in radians for rotation around that axis.
Example:
The axis to rotate around: 0.000000 0.465391 0.885105
Rotation around that axis (in radians): 3.141593
I have the conversion from radians to degrees, but I need the rotation values around XYZ from these values.
We can build a sequence for elementary matrix transformations to rotate about angle-axis. Let axis unit vector is w, angle Theta. Auxiliary values:
V = Sqrt(wx*wx + wz*wz)
W = Sqrt(wx*wx + wy*wy + wz*wz) //1 for unit dir vector
Cos(Alpha) = wz/V
Sin(Alpha) = wx/V
Cos(Beta) = V/W
Sin(Beta) = wy/W
Transformation sequence:
Ry(-Alpha) //rotation matrix about Y-axis by angle -Alpha
Rx(Beta)
Rz(Theta)
Rx(-Beta)
Ry(Alpha)
Note that for is axis is parallel to Y , one should use usual just rotation matrix about Y (accounting for direction sign), because V value is zero.
There is rather complex Rodrigues' rotation formula for computing the rotation matrix corresponding to a rotation by an angle Theta about a fixed axis specified by the unit vector w.
Explicit matrix here (weird formatted picture):
This Below C++ Function Rotates a Point Around A Provided Center and Uses Another Vector(Unit) as Axis to rotate Around.
This Code Below Was Made Thanks to Glenn Murray who provided the Formula Provided in Link. This is Tested and is Working Perfectly As intended.
Note: When Angle Is Positive And Unit Vector is {0,0,1} It Rotates To the Right Side, Basically Rotation Is On the Right Side, Axes are {x-forward,y-right,z-up}
void RotateVectorAroundPointAndAxis(Vector3D YourPoint, Vector3D PreferedCenter, Vector3D UnitDirection, float Angle, Vector3D& ReturnVector)
{
float SinVal = sin(Angle * 0.01745329251);
float CosVal = cos(Angle * 0.01745329251);
float OneMinSin = 1.0f - SinVal;
float OneMinCos = 1.0f - CosVal;
UnitDirection = GetUnitVector(UnitDirection, { 0,0,0 });// This Function Gets unit Vector From InputVector - DesiredCenter
float Temp = (UnitDirection.x * YourPoint.x) + (UnitDirection.y * YourPoint.y) + (UnitDirection.z * YourPoint.z);
ReturnVector.x = (PreferedCenter.x * (UnitDirection.y * UnitDirection.y)) - (UnitDirection.x * (((-PreferedCenter.y * UnitDirection.y) + (-PreferedCenter.z * UnitDirection.z)) - Temp));
ReturnVector.y = (PreferedCenter.y * (UnitDirection.x * UnitDirection.x)) - (UnitDirection.y * (((-PreferedCenter.x * UnitDirection.x) + (-PreferedCenter.z * UnitDirection.z)) - Temp));
ReturnVector.z = (PreferedCenter.z * (UnitDirection.x * UnitDirection.x)) - (UnitDirection.z * (((-PreferedCenter.x * UnitDirection.x) + (-PreferedCenter.y * UnitDirection.y)) - Temp));
ReturnVector.x = (ReturnVector.x * OneMinCos) + (YourPoint.x * CosVal);
ReturnVector.y = (ReturnVector.y * OneMinCos) + (YourPoint.y * CosVal);
ReturnVector.z = (ReturnVector.z * OneMinCos) + (YourPoint.z * CosVal);
ReturnVector.x += (-(PreferedCenter.z * UnitDirection.y) + (PreferedCenter.y * UnitDirection.z) - (UnitDirection.z * YourPoint.y) + (UnitDirection.y * YourPoint.z)) * SinVal;
ReturnVector.y += ( (PreferedCenter.z * UnitDirection.x) - (PreferedCenter.x * UnitDirection.z) + (UnitDirection.z * YourPoint.x) - (UnitDirection.x * YourPoint.z)) * SinVal;
ReturnVector.z += (-(PreferedCenter.y * UnitDirection.x) + (PreferedCenter.x * UnitDirection.y) - (UnitDirection.y * YourPoint.x) + (UnitDirection.x * YourPoint.y)) * SinVal;
}
I am trying to implement Bicubic image interpolation using cubic splines and the generalized formula for interpolation.
I was able to implement this by using all 16 surrounding pixels and calculate the interpolation coefficients from these 16 function values. But as interpolation is separable by definition, I tried to implement a version which uses two 1-D interpolations in order to achieve bicubic interpolation.
For the generalized interpolation formula I use e.g. http://bigwww.epfl.ch/publications/thevenaz9901.pdf chapter 3 and for the 1-D interpolation I use the idea behind this: http://www.vision-systems.com/articles/print/volume-12/issue-10/departments/wilsons-websites/understanding-image-interpolation-techniques.html
My coefficients are calculated by invertind the 4x4 matrix like in https://en.wikipedia.org/wiki/Spline_interpolation#Example
The interpolation consists of three functions.
The interpolation function itself (phi):
private float interpolate(float p_fValue)
{
p_fValue = Math.Abs(p_fValue);
if (p_fValue >= 1 && p_fValue < 2)
{
return 1.0f / 6.0f * (2.0f - p_fValue) * (2.0f - p_fValue) * (2.0f - p_fValue);//-(p_fValue * p_fValue * p_fValue) / 6.0f + p_fValue * p_fValue - 2.0f * p_fValue + 4.0f / 3.0f;
}
else if (p_fValue < 1)
{
return 2.0f / 3.0f - p_fValue * p_fValue + 0.5f * (p_fValue * p_fValue * p_fValue);
}
else
{
return 0.0f;
}
}
Calculating 4 coefficients from 4 function values:
private void calculateCoefficients(float p_f1, float p_f2, float p_f3, float p_f4, out float p_c1, out float p_c2, out float p_c3, out float p_c4)
{
p_c1 = 6.0f / 209.0f * (56.0f * p_f1 - 15.0f * p_f2 + 4.0f * p_f3 - p_f4);
p_c2 = 6.0f / 209.0f * (-15.0f * p_f1 + 60.0f * p_f2 - 16.0f * p_f3 + 4.0f * p_f4);
p_c3 = 6.0f / 209.0f * (4.0f * p_f1 - 16.0f * p_f2 + 60.0f * p_f3 - 15.0f * p_f4);
p_c4 = 6.0f / 209.0f * (-p_f1 + 4.0f * p_f2 - 15.0f * p_f3 + 56.0f * p_f4);
}
And the interpolation of a whole image from a smaller source image:
// p_siImage = original (smaller) image
// p_iImageWidth, p_iImageHeight = Image size (pixel count) of the new image
// Interpolation with standard formula u(x) = sum_{k to N} c_k * phi(x-k); For N function values
public SampledImage InterpolateImage(SampledImage p_siImage, int p_iImageWidth, int p_iImageHeight)
{
// Create interpolated image
SampledImage resultImage = new SampledImage((int)p_siImage.GetWidth(), (int)p_siImage.GetHeight(), p_iImageWidth, p_iImageHeight, p_siImage.GetPixelWidth(), p_siImage.GetPixelHeight());
for (int iX = 0; iX < p_iImageWidth; iX++)
{
for (int iY = 0; iY < p_iImageHeight; iY++)
{
// Calculate pixel position "in space"
float[] pixelSpace = resultImage.GetPixelInSpace(iX, iY);
// Calculate the index in smaller image, as real number (pixel index between pixels)
float[] pixelRealIndex = p_siImage.GetPixelRealFromSpace(pixelSpace[0], pixelSpace[1]);
// Calculate X values of surrounding pixels
int x_2 = (int)Math.Floor(pixelRealIndex[0]) - 1;
int x_1 = (int)Math.Floor(pixelRealIndex[0]);
int x1 = (int)Math.Floor(pixelRealIndex[0]) + 1;
int x2 = (int)Math.Floor(pixelRealIndex[0]) + 2;
// Calculate Y value of nearest pixel
int y = (int)Math.Floor(pixelRealIndex[1]);
// Arrays for "function values" (= color values)
float[] red = new float[4];
float[] green = new float[4];
float[] blue = new float[4];
// Create polynom for each column
for (int iiX = -1; iiX < 3; iiX++)
{
// Get column X-index
int x = (int)Math.Floor(pixelRealIndex[0]) + iiX;
// Used Y-Indices for polynom
int y_2 = (int)Math.Floor(pixelRealIndex[1]) - 1;
int y_1 = (int)Math.Floor(pixelRealIndex[1]);
int y1 = (int)Math.Floor(pixelRealIndex[1]) + 1;
int y2 = (int)Math.Floor(pixelRealIndex[1]) + 2;
// Get "function values" for each pixel in the column
Color fxy_2 = p_siImage.GetValueMirrored(x, y_2);
Color fxy_1 = p_siImage.GetValueMirrored(x, y_1);
Color fxy1 = p_siImage.GetValueMirrored(x, y1);
Color fxy2 = p_siImage.GetValueMirrored(x, y2);
// Coefficients c_k
float redC_2, redC_1, redC1, redC2;
float greenC_2, greenC_1, greenC1, greenC2;
float blueC_2, blueC_1, blueC1, blueC2;
// Calculate the coefficients for the column polynom
calculateCoefficients(fxy_2.R, fxy_1.R, fxy1.R, fxy2.R, out redC_2, out redC_1, out redC1, out redC2);
calculateCoefficients(fxy_2.G, fxy_1.G, fxy1.G, fxy2.G, out greenC_2, out greenC_1, out greenC1, out greenC2);
calculateCoefficients(fxy_2.B, fxy_1.B, fxy1.B, fxy2.B, out blueC_2, out blueC_1, out blueC1, out blueC2);
// Interpolate in each column at the same Y-Index as the actual interpolation position
red[iiX+1] = redC_2 * interpolate(pixelRealIndex[1] - (float)y_2) + redC_1 * interpolate(pixelRealIndex[1] - (float)y_1) +
redC1 * interpolate(pixelRealIndex[1] - (float)y1) + redC2 * interpolate(pixelRealIndex[1] - (float)y2);
green[iiX+1] = greenC_2 * interpolate(pixelRealIndex[1] - (float)y_2) + greenC_1 * interpolate(pixelRealIndex[1] - (float)y_1) +
greenC1 * interpolate(pixelRealIndex[1] - (float)y1) + greenC2 * interpolate(pixelRealIndex[1] - (float)y2);
blue[iiX+1] = blueC_2 * interpolate(pixelRealIndex[1] - (float)y_2) + blueC_1 * interpolate(pixelRealIndex[1] - (float)y_1) +
blueC1 * interpolate(pixelRealIndex[1] - (float)y1) + blueC2 * interpolate(pixelRealIndex[1] - (float)y2);
}
//Now: interpolate the actual value
// Get "function values" for each pixel in the row
Color fx_2y = p_siImage.GetValueMirrored(x_2, y);
Color fx_1y = p_siImage.GetValueMirrored(x_1, y);
Color fx1y = p_siImage.GetValueMirrored(x1, y);
Color fx2y = p_siImage.GetValueMirrored(x2, y);
// Coefficients c_k
float redCX_2, redCX_1, redCX1, redCX2;
float greenCX_2, greenCX_1, greenCX1, greenCX2;
float blueCX_2, blueCX_1, blueCX1, blueCX2;
// Calculate coefficients by using the interpolated values of each column
calculateCoefficients(red[0], red[1], red[2], red[3], out redCX_2, out redCX_1, out redCX1, out redCX2);
calculateCoefficients(green[0], green[1], green[2], green[3], out greenCX_2, out greenCX_1, out greenCX1, out greenCX2);
calculateCoefficients(blue[0], blue[1], blue[2], blue[3], out blueCX_2, out blueCX_1, out blueCX1, out blueCX2);
// Interpolate finally for the actual value
float redResult = redCX_2 * interpolate(pixelRealIndex[0] - (float)x_2) + redCX_1 * interpolate(pixelRealIndex[0] - (float)x_1) +
redCX1 * interpolate(pixelRealIndex[0] - (float)x1) + redCX2 * interpolate(pixelRealIndex[0] - (float)x2);
float greenResult = greenCX_2 * interpolate(pixelRealIndex[0] - (float)x_2) + greenCX_1 * interpolate(pixelRealIndex[0] - (float)x_1) +
greenCX1 * interpolate(pixelRealIndex[0] - (float)x1) + greenCX2 * interpolate(pixelRealIndex[0] - (float)x2);
float blueResult = blueCX_2 * interpolate(pixelRealIndex[0] - (float)x_2) + blueCX_1 * interpolate(pixelRealIndex[0] - (float)x_1) +
blueCX1 * interpolate(pixelRealIndex[0] - (float)x1) + blueCX2 * interpolate(pixelRealIndex[0] - (float)x2);
// Make sure to care for under/overshoots
redResult = Math.Max(0, Math.Min(redResult, 255));
greenResult = Math.Max(0, Math.Min(greenResult, 255));
blueResult = Math.Max(0, Math.Min(blueResult, 255));
// Set the pixel to the calculated value
Color resultColor = Color.FromArgb((int)redResult, (int)greenResult, (int)blueResult);
resultImage.SetValue(iX, iY, resultColor);
}
}
return resultImage;
}
For an image like this (15x15px):
The result looks like this (scaled to 80x80px):
In contrast, this is how the result looks if I calculate all 16 coefficients at once(80x80px):
My question is now:
How is the separation done correctly? Or am I missing something completely and I only can separate the interpolation function (phi) but not the calculation of the coefficients?
I am struggling with a situation, where I want to draw a circle with GDI+ and some points on it (drawn as smaller circles), but the circle seems to be noncircular. By implementing scaling and zero point shifting, I zoom into the points and the circle, and find the points not lying exactly on the circle.
When I add a 'discrete' circle drawn with line segments, this circle does fit the points very well, if enough segments are used. Since the math is the same, I think that roundoff errors in my code can not be the cause of the circle deviations (although probably in the implementation of DrawEllipse).
In fact the deviations are biggest on 45/135/225/315 degrees.
I made a small project reproducing the effect with a slightly different setup: I draw multiple circles with their origins lying on an other circle with its center on the center of the form with the same radius. If everything goes well all circles shoud touch the center of the form. But with big radii like 100'000, they dont pass throug the center anymore, but miss it by a screendistance of maybe 15 pixel.
To reproduce the situation make a new c# project, put on form1 button1, and call the function Draw() from it:
private void Draw()
{
Graphics g = this.CreateGraphics();
g.Clear(this.BackColor );
double hw = this.Width / 2;
double hh = this.Height / 2;
double scale = 100000;
double R = 1;
for (int i = 0; i < 12; i++)
{
double angle = i * 30;
double cx = R * Math.Cos(angle * Math.PI / 180);
double cy = R * Math.Sin(angle * Math.PI / 180);
g.DrawEllipse(new Pen(Color.Blue, 1f), (float)(hw - scale * (cx + R)), (float)(hh + scale * (cy - R)), (float)(2 * R * scale), (float)(2 * R * scale));
g.DrawLine(Pens.Black, new Point(0, (int)hh), new Point(this.Width, (int)hh));
g.DrawLine(Pens.Black, new Point((int)hw, 0), new Point((int)hw, this.Height));
double r = 3;
g.DrawEllipse(new Pen(Color.Green, 1f), (float)(hw - r), (float)(hh - r), (float)(2 * r), (float)(2 * r));
//Pen magpen = new Pen(Color.Magenta, 1);
//double n = 360d / 1000;
//for (double j = 0; j < 360; j += n)
//{
// double p1x = R * Math.Cos(j * Math.PI / 180) + cx;
// double p1y = R * Math.Sin(j * Math.PI / 180) + cy;
// double p2x = R * Math.Cos((j + n) * Math.PI / 180) + cx;
// double p2y = R * Math.Sin((j + n) * Math.PI / 180) + cy;
// g.DrawLine(magpen, (float)(hw - scale * p1x), (float)(hh + scale * p1y), (float)(hw - scale * p2x), (float)(hh + scale * p2y));
//}
}
}
use the variable scale from 100 to 100'000 to see the effect: The circles don't touch the origin anymore, but wobble around it. If you uncomment the commented lines you can see, that the magenta 'discrete' circle performs much better.
Since using 'discrete' circles and arcs is a performance killer, I am looking for a way to draw better circles with GDI+.
Any ideas, suggestions?
GDI+ (at least in its native version) doesn't draw ellipses: it uses the cubic Bezier approximation, as described here and here.
That is, instead of
c = cos(angle);
s = sin(angle);
x = c * radius;
y = s * radius;
or, for an ellipse (a & b as semi major and semi minor axes in some order)
x = c * a;
y = s * b;
it uses
K = (sqrt(2) - 1) * 4.0 / 3;
t = angle * 0.5 / pi;
u = 1 - t;
c = (u * u * u) * 1 + (3 * u * u * t) * 1 + (3 * u * t * t) * K + (t * t * t) * 0;
s = (u * u * u) * 0 + (3 * u * u * t) * K + (3 * u * t * t) * 1 + (t * t * t) * 1;
(for angle in the range [0, pi/2]: other quadrants can be calculated by symmetry).
g.DrawArc(new Pen(Color.Blue, 1f), (float)((hw) - R * scale), -(float)((2 * R * scale) - hh), (float)(2 * R * scale), (float)(2 * R * scale), 0, 360);
Well I got somewhat precise results with this method.However this is only a sample.