I'm starting a project where I need to control a virtual character. The character is being rendered in multiple 3D engines, such as Three.JS and iOS SceneKit.
I'm getting the Quaternions of every joint of the skeleton with OpenNI, and it looks kind of like this:
The code that saves and pass the quaternion, looks like this:
float confidence = context.getJointOrientationSkeleton(userId, jointName,
joint);
joints[jointIndex]=joint.m00;
joints[jointIndex+1]=joint.m01;
joints[jointIndex+2]=joint.m02;
joints[jointIndex+3]=joint.m10;
joints[jointIndex+4]=joint.m11;
joints[jointIndex+5]=joint.m12;
joints[jointIndex+6]=joint.m20;
joints[jointIndex+7]=joint.m21;
joints[jointIndex+8]=joint.m22;
jointIndex+=9;
This repeats for every joint of the skeleton.
The last row and last column is always [0 0 0 1] [0, 0, 0, 1], so I just append that on the client once I receive it and build a 4x4 matrix.
I want to be able to make the right rotations with this data, but the rotations I'm getting are definitely wrong.
This is how I'm processing the matrix: (pseudo-code)
row1 = [m00 m01 m02 0]
row2 = [m10 m11 m12 0]
row3 = [m20 m21 m22 0]
row4 = [0 0 0 1]
matrix4by4 = matrix4x4(rows:[row1,row2,row3,row4])
And then I got the quaternion with two methods, and both methods were showing bad rotations, I cannot find what's wrong or what I'm missing.
First method
There's an iOS function that gets a 3x3 or 4x4 matrix, and transforms it into quaternion:
boneRotation = simd_quatf.init(matrix4by4).vector //X,Y,Z,W
Second method
I found the following code on the web:
let tr = m00 + m11 + m22
var qw = 0
var qx = 0
var qy = 0
var qz = 0
if (tr > 0) {
var S = sqrt(tr+1.0) * 2 // S=4*qw
qw = 0.25 * S
qx = (m21 - m12) / S
qy = (m02 - m20) / S
qz = (m10 - m01) / S
} else if ((m00 > m11) && (m00 > m22)) {
var S = sqrt(1.0 + m00 - m11 - m22) * 2 // S=4*qx
qw = (m21 - m12) / S
qx = 0.25 * S
qy = (m01 + m10) / S
qz = (m02 + m20) / S
} else if (m11 > m22) {
var S = sqrt(1.0 + m11 - m00 - m22) * 2 // S=4*qy
qw = (m02 - m20) / S
qx = (m01 + m10) / S
qy = 0.25 * S
qz = (m12 + m21) / S
} else {
let S = sqrt(1.0 + m22 - m00 - m11) * 2 // S=4*qz
var = (m10 - m01) / S
qx = (m02 + m20) / S
qy = (m12 + m21) / S
qz = 0.25 * S
}
boneRotation = vector4(qx, qy, qw, qz)
I started testing only with shoulder and elbow rotation to help me visualize what could be wrong or missing, and made a video.
Here's how it's behaving: https://www.youtube.com/watch?v=xUtNiwH_AGk
What could I be missing? For example, the axis of rotation of the shoulder are like this:
X-Axis
Y-Axis
Z-Axis
Thank you in advance :-)
YouTuve Video: https://www.youtube.com/watch?v=xUtNiwH_AGk
Related
Could someone help me in understanding what paramaters assume to have such spiral as in this question:Draw equidistant points on a spiral?
I don't understant this parameter: rotation- Overall rotation of the spiral. ('0'=no rotation, '1'=360 degrees, '180/360'=180 degrees) I would be grateful if someone write some sets of parameters (sides,coils,rotation) to get spiral.
It's code in Matlab:
clc
clear all
centerX = 0
centerY = 0
radius = 10
coils = 30
rotation = 360
chord = 2
delta = 1
thetaMax = coils * 2 * pi;
awayStep = radius / thetaMax;
i = 1
for theta = (chord / awayStep):thetaMax;
away = awayStep * theta;
around = theta + rotation;
x(i) = centerX + cos ( around ) * away;
y(i) = centerY + sin ( around ) * away;
i = i + 1
theta = theta + (chord / away);
theta2 = theta + delta
away2 = away + awayStep * delta
delta = 2 * chord / ( away + away2 )
delta = 2 * chord / ( 2*away + awayStep * delta )
2*(away + awayStep * delta ) * delta == 2 * chord
awayStep * delta * 2 + 2*away * delta - 2 * chord == 0
a= awayStep; b = 2*away; c = -2*chord
delta = ( -2 * away + sqrt ( 4 * away * away + 8 * awayStep * chord ) ) / ( 2 * awayStep );
theta = theta + delta;
end
v = [0 x]
w = [0 y]
scatter(v,w)
Thank you in advance
I load a model with vertex and vertex nromal,
for (var i = 0, vindex = 0; i < triangle.length; i++, vindex += 3) {
x = parseFloat(triangle[i].attributes.getNamedItem('x1').value);
y = parseFloat(triangle[i].attributes.getNamedItem('y1').value);
z = parseFloat(triangle[i].attributes.getNamedItem('z1').value);
this.geometry.vertices.push(new THREE.Vector3(x * scale + this.translateVector.x, y * scale + this.translateVector.y, z * scale + this.translateVector.z));
x = parseFloat(triangle[i].attributes.getNamedItem('x2').value);
y = parseFloat(triangle[i].attributes.getNamedItem('y2').value);
z = parseFloat(triangle[i].attributes.getNamedItem('z2').value);
this.geometry.vertices.push(new THREE.Vector3(x * scale + this.translateVector.x, y * scale + this.translateVector.y, z * scale + this.translateVector.z));
x = parseFloat(triangle[i].attributes.getNamedItem('x3').value);
y = parseFloat(triangle[i].attributes.getNamedItem('y3').value);
z = parseFloat(triangle[i].attributes.getNamedItem('z3').value);
this.geometry.vertices.push(new THREE.Vector3(x * scale + this.translateVector.x, y * scale + this.translateVector.y, z * scale + this.translateVector.z));
var face = new THREE.Face3(vindex, vindex + 1, vindex + 2);
face.color.setHex(this.faceColor || this.defaultcolor);
face.vertexNormals = [];
nx = parseFloat(triangle[i].attributes.getNamedItem('nx1').value);
ny = parseFloat(triangle[i].attributes.getNamedItem('ny1').value);
nz = parseFloat(triangle[i].attributes.getNamedItem('nz1').value);
face.vertexNormals.push(new THREE.Vector3(-nx, -ny, -nz));
nx1 = parseFloat(triangle[i].attributes.getNamedItem('nx2').value);
ny1 = parseFloat(triangle[i].attributes.getNamedItem('ny2').value);
nz1 = parseFloat(triangle[i].attributes.getNamedItem('nz2').value);
face.vertexNormals.push(new THREE.Vector3(-nx1, -ny1, -nz1));
nx2 = parseFloat(triangle[i].attributes.getNamedItem('nx3').value);
ny2 = parseFloat(triangle[i].attributes.getNamedItem('ny3').value);
nz2 = parseFloat(triangle[i].attributes.getNamedItem('nz3').value);
face.vertexNormals.push(new THREE.Vector3(-nx2, -ny2, -nz2));
face.normal.set((nx + nx1 + nx2) / 3, (ny + ny1 + ny2) / 3,(nz + nz1 + nz2) / 3);
this.geometry.faces.push(face);
}
this.material = new THREE.MeshBasicMaterial({ vertexColors: THREE.FaceColors, overdraw: true , opacity: 1, transparent: 0 });
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.mesh.name = this.id;
this.mesh.updateMatrix();
this.mesh.matrixAutoUpdate = false;
scene.add(this.mesh);
the house below, front face is invisible, so front wall and left wall is invisible, we can see through the inside of house, but I want it to show all walls and not see through, could anyone help me?
after I change to Lambert material it still show house inside, I've tried, cw,ccw, or invert index of vertex, invert normal. could any body help?
it is possible there is something wrong with the face UV's. try making the material applied doublesided.
seems find the answer.
that's because part of the house model position.z < 0, and camera's near < 0, maybe three.js z-buffer clear negative = 0, z-buffer determines the sheltery relation.
Using a quaternion, if I rotate my cube along an axis by 90 degrees, I get a different front facing cube side, which appears as a straight-on square of a solid color. My cube has different colored sides, so changing the axis it is rotated along gives me these different colors as expected.
When I try to rotate by an arbitrary amount, I get quite the spectacular mess, and I don't know why since I'd expect the quaternion process to work well regardless of the angle:
I am creating a quaternion from 2 vectors using this:
inline QuaternionT<T> QuaternionT<T>::CreateFromVectors(const Vector3<T>& v0, const Vector3<T>& v1)
{
if (v0 == -v1)
return QuaternionT<T>::CreateFromAxisAngle(vec3(1, 0, 0), Pi);
Vector3<T> c = v0.Cross(v1);
T d = v0.Dot(v1);
T s = std::sqrt((1 + d) * 2);
QuaternionT<T> q;
q.x = c.x / s;
q.y = c.y / s;
q.z = c.z / s;
q.w = s / 2.0f;
return q;
}
I think the above method is fine since I've seen plenty of sample code correctly using it.
With the above method, I do this:
Quaternion quat1=Quaternion::CreateFromVectors(vec3(0,1,0), vec3(0,0,1));
It works, and it is a 90-degree rotation.
But suppose I want more like a 45-degree rotation?
Quaternion quat1=Quaternion::CreateFromVectors(vec3(0,1,0), vec3(0,1,1));
This gives me the mess above. I also tried normalizing quat1 which provides different though similarly distorted results.
I am using the quaternion as a Modelview rotation matrix, using this:
inline Matrix3<T> QuaternionT<T>::ToMatrix() const
{
const T s = 2;
T xs, ys, zs;
T wx, wy, wz;
T xx, xy, xz;
T yy, yz, zz;
xs = x * s; ys = y * s; zs = z * s;
wx = w * xs; wy = w * ys; wz = w * zs;
xx = x * xs; xy = x * ys; xz = x * zs;
yy = y * ys; yz = y * zs; zz = z * zs;
Matrix3<T> m;
m.x.x = 1 - (yy + zz); m.y.x = xy - wz; m.z.x = xz + wy;
m.x.y = xy + wz; m.y.y = 1 - (xx + zz); m.z.y = yz - wx;
m.x.z = xz - wy; m.y.z = yz + wx; m.z.z = 1 - (xx + yy);
return m;
}
Any idea what's going on here?
What does your frustum look like? If you have a distorted "lens" such as an exceptionally wide-angle field of view, then angles that actually show the depth, such as an arbitrary rotation, might not look as you expect. (Just like how a fisheye lens on a camera makes perspective look unrealistic).
Make sure you are using a realistic frustum if you want to see realistic images.
I'm writing a game in Visual Studio 2010, using the XNA 4.0 framework. I have a 3D terrain model generated from a height map. What I'm trying to accomplish is to tint this model in a given radius around a certain point, the end goal being to display to the player the radius in which a unit can move in a given turn. The method I'm using to draw the model at the moment is this:
void DrawModel(Model model, Matrix worldMatrix)
{
Matrix[] boneTransforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(boneTransforms);
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.World = boneTransforms[mesh.ParentBone.Index] * worldMatrix;
effect.View = camera.viewMatrix;
effect.Projection = camera.projectionMatrix;
effect.EnableDefaultLighting();
effect.EmissiveColor = Color.Green.ToVector3();
effect.PreferPerPixelLighting = true;
// Set the fog to match the black background color
effect.FogEnabled = true;
effect.FogColor = Color.CornflowerBlue.ToVector3();
effect.FogStart = 1000;
effect.FogEnd = 3200;
}
mesh.Draw();
}
}
Also, in case it's relevant, I followed this tutorial http://create.msdn.com/en-US/education/catalog/sample/collision_3d_heightmap to create my heightmap and terrain.
Thanks in advance for any help!
You can use a shader to achieve that...
you only would need to pass as argument the world position of the center and the radius,
and let the pixel shader receive the pixel world position interpolated from the vertex shader as a texture coord...
then only have to check the distance of the pixel position to the center and tint it with a color if the pixel position is in range...
The technique you are looking for is called decaling.
You have to extract the part of the terrain, where the circle will be drawn, apply an appropriate texture to that part and draw it blending it with the terrain.
For the case of a terrain based on a uniform grid, this will look like the following:
You have the center position of the decal and its radius. Then you can determine min and max row/col in the grid, so that the cells include every drawn region. Create a new vertex buffer from these vertices. Positions can be read from the heightmap. You have to alter the texture coordinates, so the texture will be placed at the right position. Assume, the center position has coordinate (0.5, 0.5), center position + (radius, radius) has coordinate (1, 1) and so on. With that you should be able to find an equation for the texture coordinates for each vertex.
In the above example, the top left red vertex has texture coordinates of about (-0.12, -0.05)
Then you have the subgrid of the terrain. Apply the decal texture to it. Set an appropriate depth bias (you have to try out some values). In most cases, a negative SlopeScaleDepthBias will work. Turn off texture coordinate wrapping in the sampler. Draw the subgrid.
Here's some VB SlimDX Code I wrote for that purpose:
Public Sub Init()
Verts = (Math.Ceiling(2 * Radius / TriAngleWidth) + 2) ^ 2
Tris = (Math.Ceiling(2 * Radius / TriAngleWidth) + 1) ^ 2 * 2
Dim Indices(Tris * 3 - 1) As Integer
Dim curN As Integer
Dim w As Integer
w = (Math.Ceiling(2 * Radius / TriAngleWidth) + 2)
For y As Integer = 0 To w - 2
For x As Integer = 0 To w - 2
Indices(curN) = x + y * w : curN += 1
Indices(curN) = x + (y + 1) * w : curN += 1
Indices(curN) = (x + 1) + (y) * w : curN += 1
Indices(curN) = x + (y + 1) * w : curN += 1
Indices(curN) = (x + 1) + (y + 1) * w : curN += 1
Indices(curN) = (x + 1) + y * w : curN += 1
Next
Next
VB = New Buffer(D3DDevice, New BufferDescription(Verts * VertexPosTexColor.Struct.SizeOfBytes, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, VertexPosTexColor.Struct.SizeOfBytes))
IB = New Buffer(D3DDevice, New DataStream(Indices, False, False), New BufferDescription(4 * Tris * 3, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 4))
End Sub
Public Sub Update()
Dim Vertex(Verts - 1) As VertexPosTexColor.Struct
Dim curN As Integer
Dim rad As Single 'The decal radius
Dim height As Single
Dim p As Vector2
Dim yx, yz As Integer
Dim t As Vector2 'texture coordinates
Dim center As Vector2 'decal center
For y As Integer = Math.Floor((center.Y - rad) / TriAngleWidth) To Math.Floor((center.Y - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1
For x As Integer = Math.Floor((center.X - rad) / TriAngleWidth) To Math.Floor((center.X - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1
p.X = x * TriAngleWidth
p.Y = y * TriAngleWidth
yx = x : yz = y
If yx < 0 Then yx = 0
If yx > HeightMap.GetUpperBound(0) Then yx = HeightMap.GetUpperBound(0)
If yz < 0 Then yz = 0
If yz > HeightMap.GetUpperBound(1) Then yz = HeightMap.GetUpperBound(1)
height = HeightMap(yx, yz)
t.X = (p.X - center.X) / (2 * rad) + 0.5
t.Y = (p.Y - center.Y) / (2 * rad) + 0.5
Vertex(curN) = New VertexPosTexColor.Struct With {.Position = New Vector3(p.X, hoehe, p.Y), .TexCoord = t, .Color = New Color4(1, 1, 1, 1)} : curN += 1
Next
Next
Dim data = D3DContext.MapSubresource(VB, MapMode.WriteDiscard, MapFlags.None)
data.Data.WriteRange(Vertex)
D3DContext.UnmapSubresource(VB, 0)
End Sub
And here's the according C# code.
public void Init()
{
Verts = Math.Pow(Math.Ceiling(2 * Radius / TriAngleWidth) + 2, 2);
Tris = Math.Pow(Math.Ceiling(2 * Radius / TriAngleWidth) + 1, 2) * 2;
int[] Indices = new int[Tris * 3];
int curN;
int w;
w = (Math.Ceiling(2 * Radius / TriAngleWidth) + 2);
for(int y = 0; y <= w - 2; ++y)
{
for(int x = 0; x <= w - 2; ++x)
{
Indices[curN] = x + y * w ; curN += 1;
Indices[curN] = x + (y + 1) * w ; curN += 1;
Indices[curN] = (x + 1) + (y) * w ; curN += 1;
Indices[curN] = x + (y + 1) * w ; curN += 1;
Indices[curN] = (x + 1) + (y + 1) * w ; curN += 1;
Indices[curN] = (x + 1) + y * w ; curN += 1;
}
}
VB = new Buffer(D3DDevice, new BufferDescription(Verts * VertexPosTexColor.Struct.SizeOfBytes, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, VertexPosTexColor.Struct.SizeOfBytes));
IB = new Buffer(D3DDevice, new DataStream(Indices, False, False), new BufferDescription(4 * Tris * 3, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 4));
}
public void Update()
{
VertexPosTexColor.Struct[] Vertex = new VertexPosTexColor.Struct[Verts] ;
int curN;
float rad; //The decal radius
float height;
Vector2 p;
int yx, yz;
Vector2 t; //texture coordinates
Vector2 center; //decal center
for(int y = Math.Floor((center.Y - rad) / TriAngleWidth); y <= Math.Floor((center.Y - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1; ++y)
for(int x = Math.Floor((center.X - rad) / TriAngleWidth); x <= Math.Floor((center.X - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1; ++x)
{
p.X = x * TriAngleWidth;
p.Y = y * TriAngleWidth;
yx = x ; yz = y;
if( yx < 0)
yx = 0;
if (yx > HeightMap.GetUpperBound(0))
yx = HeightMap.GetUpperBound(0);
if (yz < 0)
yz = 0;
if (yz > HeightMap.GetUpperBound(1))
yz = HeightMap.GetUpperBound(1);
height = HeightMap[yx, yz];
t.X = (p.X - center.X) / (2 * rad) + 0.5;
t.Y = (p.Y - center.Y) / (2 * rad) + 0.5;
Vertex[curN] = new VertexPosTexColor.Struct() {Position = new Vector3(p.X, hoehe, p.Y), TexCoord = t, Color = New Color4(1, 1, 1, 1)}; curN += 1;
}
}
var data = D3DContext.MapSubresource(VB, MapMode.WriteDiscard, MapFlags.None);
data.Data.WriteRange(Vertex);
D3DContext.UnmapSubresource(VB, 0);
}
I'm trying to find the best way to calculate the biggest (in area) rectangle which can be contained inside a rotated rectangle.
Some pictures should help (I hope) in visualizing what I mean:
The width and height of the input rectangle is given and so is the angle to rotate it. The output rectangle is not rotated or skewed.
I'm going down the longwinded route which I'm not even sure if it will handle the corner cases (no pun intended). I'm certain there is an elegant solution to this. Any tips?
EDIT: The output rectangle points don't necessarily have to touch the input rectangles edges. (Thanks to Mr E)
I just came here looking for the same answer. After shuddering at the thought of so much math involved, I thought I would resort to a semi-educated guess. Doodling a bit I came to the (intuitive and probably not entirely exact) conclusion that the largest rectangle is proportional to the outer resulting rectangle, and its two opposing corners lie at the intersection of the diagonals of the outer rectangle with the longest side of the rotated rectangle. For squares, any of the diagonals and sides would do... I guess I am happy enough with this and will now start brushing the cobwebs off my rusty trig skills (pathetic, I know).
Minor update... Managed to do some trig calculations. This is for the case when the Height of the image is larger than the Width.
Update. Got the whole thing working. Here is some js code. It is connected to a larger program, and most variables are outside the scope of the functions, and are modified directly from within the functions. I know this is not good, but I am using this in an isolated situation, where there will be no confusion with other scripts: redacted
I took the liberty of cleaning the code and extracting it to a function:
function getCropCoordinates(angleInRadians, imageDimensions) {
var ang = angleInRadians;
var img = imageDimensions;
var quadrant = Math.floor(ang / (Math.PI / 2)) & 3;
var sign_alpha = (quadrant & 1) === 0 ? ang : Math.PI - ang;
var alpha = (sign_alpha % Math.PI + Math.PI) % Math.PI;
var bb = {
w: img.w * Math.cos(alpha) + img.h * Math.sin(alpha),
h: img.w * Math.sin(alpha) + img.h * Math.cos(alpha)
};
var gamma = img.w < img.h ? Math.atan2(bb.w, bb.h) : Math.atan2(bb.h, bb.w);
var delta = Math.PI - alpha - gamma;
var length = img.w < img.h ? img.h : img.w;
var d = length * Math.cos(alpha);
var a = d * Math.sin(alpha) / Math.sin(delta);
var y = a * Math.cos(gamma);
var x = y * Math.tan(gamma);
return {
x: x,
y: y,
w: bb.w - 2 * x,
h: bb.h - 2 * y
};
}
I encountered some problems with the gamma-calculation, and modified it to take into account in which direction the original box is the longest.
-- Magnus Hoff
Trying not to break tradition putting the solution of the problem as a picture:)
Edit:
Third equations is wrong. The correct one is:
3.w * cos(α) * X + w * sin(α) * Y - w * w * sin(α) * cos(α) - w * h = 0
To solve the system of linear equations you can use Cramer rule, or Gauss method.
First, we take care of the trivial case where the angle is zero or a multiple of pi/2. Then the largest rectangle is the same as the original rectangle.
In general, the inner rectangle will have 3 points on the boundaries of the outer rectangle. If it does not, then it can be moved so that one vertex will be on the bottom, and one vertex will be on the left. You can then enlarge the inner rectangle until one of the two remaining vertices hits a boundary.
We call the sides of the outer rectangle R1 and R2. Without loss of generality, we can assume that R1 <= R2. If we call the sides of the inner rectangle H and W, then we have that
H cos a + W sin a <= R1
H sin a + W cos a <= R2
Since we have at least 3 points on the boundaries, at least one of these inequality must actually be an equality. Let's use the first one. It is easy to see that:
W = (R1 - H cos a) / sin a
and so the area is
A = H W = H (R1 - H cos a) / sin a
We can take the derivative wrt. H and require it to equal 0:
dA/dH = ((R1 - H cos a) - H cos a) / sin a
Solving for H and using the expression for W above, we find that:
H = R1 / (2 cos a)
W = R1 / (2 sin a)
Substituting this in the second inequality becomes, after some manipulation,
R1 (tan a + 1/tan a) / 2 <= R2
The factor on the left-hand side is always at least 1. If the inequality is satisfied, then we have the solution. If it isn't satisfied, then the solution is the one that satisfies both inequalities as equalities. In other words: it is the rectangle which touches all four sides of the outer rectangle. This is a linear system with 2 unknowns which is readily solved:
H = (R2 cos a - R1 sin a) / cos 2a
W = (R1 cos a - R2 sin a) / cos 2a
In terms of the original coordinates, we get:
x1 = x4 = W sin a cos a
y1 = y2 = R2 sin a - W sin^2 a
x2 = x3 = x1 + H
y3 = y4 = y2 + W
Edit: My Mathematica answer below is wrong - I was solving a slightly different problem than what I think you are really asking.
To solve the problem you are really asking, I would use the following algorithm(s):
On the Maximum Empty Rectangle Problem
Using this algorithm, denote a finite amount of points that form the boundary of the rotated rectangle (perhaps a 100 or so, and make sure to include the corners) - these would be the set S decribed in the paper.
.
.
.
.
.
For posterity's sake I have left my original post below:
The inside rectangle with the largest area will always be the rectangle where the lower mid corner of the rectangle (the corner near the alpha on your diagram) is equal to half of the width of the outer rectangle.
I kind of cheated and used Mathematica to solve the algebra for me:
From this you can see that the maximum area of the inner rectangle is equal to 1/4 width^2 * cosecant of the angle times the secant of the angle.
Now I need to figure out what is the x value of the bottom corner for this optimal condition. Using the Solve function in mathematica on my area formula, I get the following:
Which shows that the x coordinate of the bottom corner equals half of the width.
Now just to make sure, I'll going to test our answer empirically. With the results below you can see that indeed the highest area of all of my tests (definately not exhaustive but you get the point) is when the bottom corner's x value = half of the outer rectangle's width.
#Andri is not working correctly for image where width > height as I tested.
So, I fixed and optimized his code by such way (with only two trigonometric functions):
calculateLargestRect = function(angle, origWidth, origHeight) {
var w0, h0;
if (origWidth <= origHeight) {
w0 = origWidth;
h0 = origHeight;
}
else {
w0 = origHeight;
h0 = origWidth;
}
// Angle normalization in range [-PI..PI)
var ang = angle - Math.floor((angle + Math.PI) / (2*Math.PI)) * 2*Math.PI;
ang = Math.abs(ang);
if (ang > Math.PI / 2)
ang = Math.PI - ang;
var sina = Math.sin(ang);
var cosa = Math.cos(ang);
var sinAcosA = sina * cosa;
var w1 = w0 * cosa + h0 * sina;
var h1 = w0 * sina + h0 * cosa;
var c = h0 * sinAcosA / (2 * h0 * sinAcosA + w0);
var x = w1 * c;
var y = h1 * c;
var w, h;
if (origWidth <= origHeight) {
w = w1 - 2 * x;
h = h1 - 2 * y;
}
else {
w = h1 - 2 * y;
h = w1 - 2 * x;
}
return {
w: w,
h: h
}
}
UPDATE
Also I decided to post the following function for proportional rectange calculating:
calculateLargestProportionalRect = function(angle, origWidth, origHeight) {
var w0, h0;
if (origWidth <= origHeight) {
w0 = origWidth;
h0 = origHeight;
}
else {
w0 = origHeight;
h0 = origWidth;
}
// Angle normalization in range [-PI..PI)
var ang = angle - Math.floor((angle + Math.PI) / (2*Math.PI)) * 2*Math.PI;
ang = Math.abs(ang);
if (ang > Math.PI / 2)
ang = Math.PI - ang;
var c = w0 / (h0 * Math.sin(ang) + w0 * Math.cos(ang));
var w, h;
if (origWidth <= origHeight) {
w = w0 * c;
h = h0 * c;
}
else {
w = h0 * c;
h = w0 * c;
}
return {
w: w,
h: h
}
}
Coproc solved this problem on another thread (https://stackoverflow.com/a/16778797) in a simple and efficient way. Also, he gave a very good explanation and python code there.
Below there is my Matlab implementation of his solution:
function [ CI, T ] = rotateAndCrop( I, ang )
%ROTATEANDCROP Rotate an image 'I' by 'ang' degrees, and crop its biggest
% inner rectangle.
[h,w,~] = size(I);
ang = deg2rad(ang);
% Affine rotation
R = [cos(ang) -sin(ang) 0; sin(ang) cos(ang) 0; 0 0 1];
T = affine2d(R);
B = imwarp(I,T);
% Largest rectangle
% solution from https://stackoverflow.com/a/16778797
wb = w >= h;
sl = w*wb + h*~wb;
ss = h*wb + w*~wb;
cosa = abs(cos(ang));
sina = abs(sin(ang));
if ss <= 2*sina*cosa*sl
x = .5*min([w h]);
wh = wb*[x/sina x/cosa] + ~wb*[x/cosa x/sina];
else
cos2a = (cosa^2) - (sina^2);
wh = [(w*cosa - h*sina)/cos2a (h*cosa - w*sina)/cos2a];
end
hw = flip(wh);
% Top-left corner
tl = round(max(size(B)/2 - hw/2,1));
% Bottom-right corner
br = tl + round(hw);
% Cropped image
CI = B(tl(1):br(1),tl(2):br(2),:);
sorry for not giving a derivation here, but I solved this problem in Mathematica a few days ago and came up with the following procedure, which non-Mathematica folks should be able to read. If in doubt, please consult http://reference.wolfram.com/mathematica/guide/Mathematica.html
The procedure below returns the width and height for a rectangle with maximum area that fits into another rectangle of width w and height h that has been rotated by alpha.
CropRotatedDimensionsForMaxArea[{w_, h_}, alpha_] :=
With[
{phi = Abs#Mod[alpha, Pi, -Pi/2]},
Which[
w == h, {w,h} Csc[phi + Pi/4]/Sqrt[2],
w > h,
If[ Cos[2 phi]^2 < 1 - (h/w)^2,
h/2 {Csc[phi], Sec[phi]},
Sec[2 phi] {w Cos[phi] - h Sin[phi], h Cos[phi] - w Sin[phi]}],
w < h,
If[ Cos[2 phi]^2 < 1 - (w/h)^2,
w/2 {Sec[phi], Csc[phi]},
Sec[2 phi] {w Cos[phi] - h Sin[phi], h Cos[phi] - w Sin[phi]}]
]
]
Here is the easiest way to do this... :)
Step 1
//Before Rotation
int originalWidth = 640;
int originalHeight = 480;
Step 2
//After Rotation
int newWidth = 701; //int newWidth = 654; //int newWidth = 513;
int newHeight = 564; //int newHeight = 757; //int newHeight = 664;
Step 3
//Difference in height and width
int widthDiff ;
int heightDiff;
int ASPECT_RATIO = originalWidth/originalHeight; //Double check the Aspect Ratio
if (newHeight > newWidth) {
int ratioDiff = newHeight - newWidth;
if (newWidth < Constant.camWidth) {
widthDiff = (int) Math.floor(newWidth / ASPECT_RATIO);
heightDiff = (int) Math.floor((originalHeight - (newHeight - originalHeight)) / ASPECT_RATIO);
}
else {
widthDiff = (int) Math.floor((originalWidth - (newWidth - originalWidth) - ratioDiff) / ASPECT_RATIO);
heightDiff = originalHeight - (newHeight - originalHeight);
}
} else {
widthDiff = originalWidth - (originalWidth);
heightDiff = originalHeight - (newHeight - originalHeight);
}
Step 4
//Calculation
int targetRectanleWidth = originalWidth - widthDiff;
int targetRectanleHeight = originalHeight - heightDiff;
Step 5
int centerPointX = newWidth/2;
int centerPointY = newHeight/2;
Step 6
int x1 = centerPointX - (targetRectanleWidth / 2);
int y1 = centerPointY - (targetRectanleHeight / 2);
int x2 = centerPointX + (targetRectanleWidth / 2);
int y2 = centerPointY + (targetRectanleHeight / 2);
Step 7
x1 = (x1 < 0 ? 0 : x1);
y1 = (y1 < 0 ? 0 : y1);
This is just an illustration of Jeffrey Sax's solution above, for my future reference.
With reference to the diagram above, the solution is:
(I used the identity tan(t) + cot(t) = 2/sin(2t))