I need to know what the visible height of a display object will be after I change it's rotationX value.
I have an application that allows users to lay out a floor in 3D space. I want the size of the floor to automatically stretch after a 3D rotation so that it always covers a certain area.
Anyone know a formula for working this out?
EDIT: I guess what I am really trying to do is convert degrees to pixels.
On a 2D plane say 100 x 100 pixels, a -10 degree change on rotationX means that the plane has a gap at the top where it is no longer visible. I want to know how many pixels this gap will be so that I can stretch the plane.
In Flex, the value for the display objects height property remains the same both before and after applying the rotation, which may in fact be a bug.
EDIT 2: There must be a general math formula to work this out rather than something Flash/Flex specific. When viewing an object in 3D space, if the object rotates backwards (top of object somersaults away from the viewer), what would the new visible height be based on degrees of rotation? This could be in pixels, metres, cubits or whatever.
I don't have a test case, but off the top of my head I'd guess something like:
var d:DisplayObject;
var rotationRadians:Number = d.rotationX * Math.PI / 180;
var visibleHeight:Number = d.height * Math.cos(rotationRadians);
This doesn't take any other transformations into account, though.
Have you tried using the object's bounding rectangle and testing that?
var dO:DisplayObject = new DisplayObject();
dO.rotation = 10;
var rect:Rectangle = dO.getRect();
// rect.topLeft.y is now the new top point.
// rect.width is the new width.
// rect.height is the new height.
As to the floor, I would need more information, but have you tried setting floor.percentWidth = 100? That might work.
Have you checked DisplayObject.transform.pixelBounds? I haven't tried it, but it might be more likely to take the rotation into account.
Rotation actually changes DisplayObject's axis's (i.e. x and y axes are rotated). That is why you are not seeing the difference in height. So for getting the visual height and y you might try this.var dO:DisplayObject = new DisplayObject();
addChild();
var rect1:Rectangle = dO.getRect(dO.parent);
dO.rotation = 10;
var rect2:Rectangle = dO.getRect(dO.parent);
rect1 and rect2 should be different in this case. If you want to check the visual coordinates of the dO then just change dO.parent with root.
Related
I have been trying to create a simple application whereby a 360 image is shown on a Sphere. The next step is to dynamically add 'hotspots' onto this image as the user pans around.
I've tried many different approaches (including delving deep deep into Three.JS) but it doesn't seem to be that easy. (UVs, positions, vertices etc).
A-Frame is awesome at making it super simple for developers to get up and running. I managed to find an example whereby there were two 'hotspots' on a sphere, which when hovered/focused, the sphere background changed.
I'm looking to create a new 'hotspot' in the cameras direction, however this is proving to be quite difficult because the camera position never changes but the rotation does.
For example, after panning, I see the following:
<a-camera position="0 2 4" camera="" look-controls="" wasd-controls="" rotation="29.793805346802827 -15.699043586584567 0">
<a-cursor color="#4CC3D9" fuse="true" timeout="10" material="color:#4CC3D9;shader:flat;opacity:0.8" cursor="maxDistance:1000;fuse:true;timeout:10" geometry="primitive:ring;radiusOuter:0.016;radiusInner:0.01;segmentsTheta:64" position="0 0 -1" raycaster=""></a-cursor>
</a-camera>
Ideally, to create a new hotspot in this position, I assumed all I'd need to do it add the rotation values to the position value and that'd be the correct place for the hotspot, but unfortunately, this doesn't work, either the hotspot is in the wrong position, it's not facing the camera or its no longer in view.
How can I create a 'hotspot' in the cameras viewpoint (in the middle)?
I've setup a codepen which should help show the problem I'm having better. Ideally when I pan around and click 'create hotspot', a 'hotspot' should be created directly in the middle (just like the red and yellow ones).
Thanks!
UPDATE: An idea I've had is to have a secondary sphere (smaller radius) with no background. If it's easy to know the XYZ coordinates of the camera view intersecting it, then this is made super simple. Is there a way of finding this out?
Without any intersection, when you know the position of the camera, this is the vector which needs to be: negated, normalized, multiplied by a scalar value which equals to the radius of the sphere. The result is the point of your new hotspot.
var pos = camera.position.clone().negate().normalize().multiplyScalar(sphereRadius);
jsfiddle example
Yes, I think your second idea is right, you can make a new sphere which has seam position with your camera. If you want to make a new 'hotspot' on your center of screen. you can make a raycaster from camera to the 'hotspot' and it will intersect the sphere. But, you should set the material's side property with 'doubleside', if not, you won't intersect the sphere. then, you can get the intersect[0].point.
var pos = new THREE.Vector3().copy(cameraEl.getComputedAttribute("position"));
cameraEl.object3D.localToWorld(pos);
var vector = new THREE.Vector3(( event.clientX / window.inneenter code hererWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
vector = vector.unproject(camera);
var raycaster = new THREE.Raycaster(pos, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects([sphere],true);
if(intersects.length > 0)
{
console.log(intersects[0].point);
}
`
I have created an logarithmic spiral in canvas, and plotted circles along it. Using your mouse scroll wheel you can zoom in and out of the spiral (which works) ā but I am having problems updating the size of the circles to match the zoom level... I'm no math expert!
There are two values I am using when trying to calculate the circle radius:
The initial size: This makes circles smaller the further down the spiral they are. I think I have this pretty close.
The growth size: This is the amount that each circle must increase to accurately grow in size as it gets closer to the viewer. Currently circles seem to be the correct size at the beginning and end of the spiral, but are too small in the middle.
I have hacked together some janky math and I'm sure there is an actual formula for this sort of sizing. Any help would be greatly appreciated ā I just want the circles to feel "attached" to the spiral and scale appropriately.
Here is the jsFiddle for reference
// a = starting radius of spiral
// spiralNum = spiral length (100.6)
// timeOffset = scroll position
// node_count_visible = number of total circles
offset = (this.spiralNum - 0.05 - this.node_count_visible) + id + (this.timeOffset/30);
var initial = Math.exp(b * offset)/4;
var growth = (a/8.5);
node.radius = initial + growth;
Thank you in advance for any help provided...
I was able to get an affect you are looking for by doing
node.radius = a * Math.exp(b * offset)/6;
6 is an arbitrary number to adjust the size of the circle.
I am implementing a pan tool in our software's 3D view which is supposed to work much like the grab tool of, say, Photoshop or Acrobat Reader. That is, the point the user grabs onto with the mouse (clicks and holds, then moves the mouse) stays under the mouse cursor as the mouse moves.
This is a common paradigm and one that's been asked about on SO before, the best answer being to this question about the technique in OpenGL. There is another that also has some hints, and I have been reading this very informative CodeProject article. (It doesn't explain many of its code examples' variables etc, but from reading the text I think I understand the technique.) But, I have some implementation issues because my 3D environment's navigation is set up quite differently to those articles, and I am seeking some guidance.
My technique - and this might be fundamentally flawed, so please say so - is:
The scene 'camera' is stored as two D3DXVECTOR3 points: the eye position and a look point. The view matrix is constructed using D3DXMatrixLookAtLH like so:
const D3DXVECTOR3 oUpVector(0.0f, 1.0f, 0.0f); // Keep up "up", always.
D3DXMatrixLookAtLH(&m_oViewMatrix, &m_oEyePos, &m_oLook, &oUpVector);
When the mouse button is pressed, shoot a ray through that pixel and find: the coordinate (in unprojected scene / world space) of the pixel that was clicked on; the intersection of that ray with the near plane; and the distance between the near-plane point and object, which is the length between those two points. Store this and the mouse position, and the original navigation (eye and look).
// Get the clicked-on point in unprojected (normal) world space
D3DXVECTOR3 o3DPos;
if (Get3DPositionAtMouse(roMousePos, o3DPos)) { // fails if nothing under the mouse
// Mouse location when panning started
m_oPanMouseStartPos = roMousePos;
// Intersection at near plane (z = 0) of the ray from camera to clicked spot
D3DXVECTOR3 oRayVector;
CalculateRayFromPixel(m_oPanMouseStartPos, m_oPanPlaneZ0StartPos, oRayVector);
// Store original eye and look points
m_oPanOriginalEyePos = m_oEyePos;
m_oPanOriginalLook = m_oLook;
// Store the distance between near plane and the object, and the object position
m_dPanPlaneZ0ObjectDist = fabs(D3DXVec3Length(&(o3DPos - m_oPanPlaneZ0StartPos)));
m_oPanOriginalObjectPos = o3DPos;
Get3DPositionAtMouse is a known-ok method which picks a 3D coordinate under the mouse. CalculateRayFromPixel is a known-ok method which takes in a screen-space mouse coordinate and casts a ray, and fills the other two parameters with the ray intersection at the near plane (Z = 0) and the normalised ray vector.
When the mouse moves, cast another ray at the new position, but using the old (original) view matrix. (Thanks to Nico below for pointing this out.) Calculate where the object should be by extending the ray from the near plane the distance between the object and near plane (this way, the original object and new object points should be in parallel plane to the near plane.) Move the eye and look coordinates by this much. Eye and Look are set from their original (when panning started) values, with the difference being from the original mouse and new mouse positions. This is to reduce any precision loss from incrementing or decrementing by granular (integer) pixel movements as the mouse moves, ie it calculates the whole difference in navigation every time.
// Set navigation back to original (as it was when started panning) and cast a ray for the mouse
m_oEyePos = m_oPanOriginalEyePos;
m_oLook = m_oPanOriginalLook;
UpdateView();
D3DXVECTOR3 oRayVector;
D3DXVECTOR3 oNewPlaneZPos;
CalculateRayFromPixel(roMousePos, oNewPlaneZPos, oRayVector);
// Now intersect that ray (ray through the mouse pixel, using the original navigation)
// to hit the plane the object is in. Function uses a "line", so start at near plane
// and the line is of the length of the far plane away
D3DXVECTOR3 oNew3DPos;
D3DXPlaneIntersectLine(&oNew3DPos, &m_oPanObjectPlane, &oNewPlaneZPos, &(oRayVector * GetScene().GetFarPlane()));
// The eye/look difference /should/ be as simple as:
// const D3DXVECTOR3 oDiff = (m_oPanOriginalObjectPos - oNew3DPos);
// But that lags and is slow, ie the objects trail behind. I don't know why. What does
// work is to scale the from-to difference by the distance from the camera relative to
// the whole scene distance
const double dDist = D3DXVec3Length(&(oNew3DPos - m_oPanOriginalEyePos));
const double dTotalDist = GetScene().GetFarPlane() - GetScene().GetNearPlane();
const D3DXVECTOR3 oDiff = (m_oPanOriginalObjectPos - oNew3DPos) * (1.0 + (dDist / dTotalDist));
// Adjust the eye and look points by the same amount, so orthogonally changed
m_oEyePos = m_oPanOriginalEyePos + oDiff;
m_oLook = m_oPanOriginalLook + oDiff;
Diagram
This diagram is my working sketch for implementing this:
and hopefully explains the above much more simply than the text. You can see a moving point, and where the camera has to move to keep that point at the same relative position. The clicked-on point (the ray from the camera to the object) is just to the right of the straight-ahead ray representing the center pixel.
The problem
But, as you've probably guessed, this doesn't work as I hope. What I wanted to see was the clicked-on object moving with the mouse cursor. What I actually see is that the object moves in the direction of the mouse, but not enough, ie it does not keep the clicked-on point under the cursor. Secondly, the movement flickers and jumps around, jittering by up to twenty or thirty pixels sometimes, then flickers back. If I replace oDiff with something constant this doesn't occur.
Any ideas, or code samples showing how to implement this with DirectX (D3DX, DX matrix order, etc) will be gratefully read.
Edit
Commenter Nico below pointed out that when calculating the new position using the mouse cursor's moved position, I needed to use the original view matrix. Doing so helps a lot, and the objects stay near the mouse position. However, it's still not exact. What I've noticed is that at the center of the screen, it is exact; as the mouse moves further from the center, it gets out by more and more. This seemed to change based on how far away the object was, too. By pure 'I have no idea what I'm doing' guesswork, I scaled this by a factor of the near/far plane and how far away the object was, and this brings it very close to the mouse cursor, but still a few pixels away (1 to, say, 30 at the extreme edge of the screen, which is enough to make it feel wrong.)
Here's how i solve this problem.
float fieldOfView = 45.0f;
float halfFOV = (fieldOfView / 2.0f) * (DEGREES_TO_RADIANS);
float distanceToObject = // compute the world space distance from the camera to the object you want to pan
float projectionToWorldScale = distanceToObject * tan( halfFov );
Vector mouseDeltaInScreenSpace = // the delta mouse in pixels that we want to pan
Vector mouseDeltaInProjectionSpace = Vector( mouseDeltaInScreenSpace.x * 2 / windowPixelSizeX, mouseDeltaInScreenSpace.y * 2 / windowPixelSizeY ); // ( the "*2" is because the projection space is from -1 to 1)
// go from normalized device coordinate space to world space (at origin)
Vector cameraDelta = -mouseDeltaInProjectionSpace * projectionToWorldScale;
// now translate your camera by "cameraDelta".
Note this works for an field of view apsect ratio of 1, i think you would have to break up the "scale" into separate x and y components if they vertical field of view was different than the horizontal field of view
Also, you mentioned a "look at" vector. I'm not sure how my math would need to change for that since my camera is always looking straight down the z-axis.
One problem is your calculation of the new 3d position. I am not sure if this is the root cause, but you might try it. If it doesn't help, just post a comment.
The problem is that your offset vector is not parallel to the znear plane. This is because the two rays are not parallel. Therefore, if the have the same length behind znear, the distance of the end point to the znear plane cannot be equal.
You can calculate the offset vector with the theorem of intersecting lines. If zNearA and zNearB are the intersection points of the znear plane with ray A and ray B respectively, then the theorem states:
Length(original_position - cam_position) / Length(offset_vector) = Length(zNearA - cam_position) / Length(zNearB - zNearA)
And therefore
offset_vector = Length(original_position - cam_position) / Length(zNearA - cam_position) * (zNearB - zNearA)
Then you can be sure to move on a line that is parallel to the znear plane.
Just try it out and see if it helps.
I used the Qt equivalent to the gluLookAt to set my view matrix and I've been moving it by translating it everywhere in the scene.. now I want to get close with the camera to an object.
I know the position of the object, both in object coords and in each other coords (I have the model matrix for that object), but how to get the position of the camera?
To animate the camera to get closer and closer to the object I suppose I should take two points:
The point where the object is
The point where the camera is
and then do something like
QVector3D direction_to_get_closer = point_where_object_is - point_where_camera_is
How do I get the point where the camera is? Or, alternatively if this is not needed, how do I get the vector to the direction the camera has to follow (no rotations, I just need translations, this is going to simplify things) to reach the object?
gluLookAt(eye, target, headUp) takes three parameters, the position of the camera/eye, the position of the object you want to look at, and a unitvector to controll roll/head up direction.
To zoom closer, you can move the eye/camera position by some fraction of your vector direction_to_get_closer. For instance,
point_where_camera_is += 0.1f * direction_to_get_closer; // move 10% closer
Its more useful to move by a constant amount instead of 10% of the current distance (or else you will move very fast when the distance is great, and then increasingly slower). Therefore, you should use the normalized direction:
QVector3D unitDir = direction_to_get_closer.normalized();
point_where_camera_is += 0.1f * unitDir; // move 0.1 units in direction
The camera transform will break if point_where_camera_is becomes equal to point_where_object_is.
A better way, if you don't need to zoom, translate/rotate the new "zoomed" point_where_camera_is is to interpolate between to positions.
float t = some user input value between 0 and 1 (0% to 100% of the line camToObj)
QVector3D point_on_line_cam_obj = t * point_where_camera_is + (1-t) * point_where_object_is;
This way, you can stop the user from zooming into the object by limiting t, also, you can go back to the start position with t=0;
See also: Why is my image rotation algorithm not working?
This question isn't language specific, and is a math problem. I will however use some C++ code to explain what I need as I'm not experienced with the mathematic equations needed to express the problem (but if you know about this, Iād be interested to learn).
Here's how the image is composed:
ImageMatrix image;
image[0][0][0] = 1;
image[0][1][0] = 2;
image[0][2][0] = 1;
image[1][0][0] = 0;
image[1][1][0] = 0;
image[1][2][0] = 0;
image[2][0][0] = -1;
image[2][1][0] = -2;
image[2][2][0] = -1;
Here's the prototype for the function I'm trying to create:
ImageMatrix rotateImage(ImageMatrix image, double angle);
I'd like to rotate only the first two indices (rows and columns) but not the channel.
The usual way to solve this is by doing it backwards. Instead of calculating where each pixel in the input image ends up in the output image, you calculate where each pixel in the output image is located in the input image (by rotationg the same amount in the other direction. This way you can be sure that all pixels in the output image will have a value.
output = new Image(input.size())
for each pixel in input:
{
p2 = rotate(pixel, -angle);
value = interpolate(input, p2)
output(pixel) = value
}
There are different ways to do interpolation. For the formula of rotation I think you should check https://en.wikipedia.org/wiki/Rotation_matrix#In_two_dimensions
But just to be nice, here it is (rotation of point (x,y) angle degrees/radians):
newX = cos(angle)*x - sin(angle)*y
newY = sin(angle)*x + cos(angle)*y
To rotate an image, you create 3 points:
A----B
|
|
C
and rotate that around A. To get the new rotated image you do this:
rotate ABC around A in 2D, so this is a single euler rotation
traverse in the rotated state from A to B. For every pixel you traverse also from left to right over the horizontal line in the original image. So if the image is an image of width 100, height 50, you'll traverse from A to B in 100 steps and from A to C in 50 steps, drawing 50 lines of 100 pixels in the area formed by ABC in their rotated state.
This might sound complicated but it's not. Please see this C# code I wrote some time ago:
rotoZoomer by me
When drawing, I alter the source pointers a bit to get a rubber-like effect, but if you disable that, you'll see the code rotates the image without problems. Of course, on some angles you'll get an image which looks slightly distorted. The sourcecode contains comments what's going on so you should be able to grab the math/logic behind it easily.
If you like Java better, I also have made a java version once, 14 or so years ago ;) ->
http://www.xs4all.nl/~perseus/zoom/zoom.java
Note there's another solution apart from rotation matrices, that doesn't loose image information through aliasing.
You can separate 2D image rotation into skews and scalings, which preserve the image quality.
Here's a simpler explanation
It seems like the example you've provided is some edge detection kernel. So if what you want to is detect edges of different angles you'd better choose some continuous function (which in your case might be a parametrized gaussian of x1 multiplied by x2) and then rotate it according to formulae provided by kigurai. As a result you would be able to produce a diskrete kernel more efficiently and without aliasing.