MotionEvent coordinates to Scene coordinates - touch

I am developing an application using multi-touch in AndEngine. To get the coordinates of touching fingers I use pSceneTouchEvent.getMotionEvent().getX([pointer index]) because TouchEvent doesn't have an equivalent getter, only pSceneTouchEvent.getX().
The problem is that MotionEvent returns coordinates on the screen while TouchEvent returns coordinates on the Scene. When I zoom in or out, the screen and Scene coordinates do not match, so I need to convert the screen coordinates to Scene coordinates. How can I do that?

Faced with the same problem and have a solution. First I want to explain it and then will paste the convert method.
The problem
Coordinates you want to have is scene coordinates which are related to your scene start point (0,0) (i.e. AndEngine anchorCenter is bottom-left). Bounds: from (0,0) to [Camera.getSurfaceWidth(), Camera.getSurfaceHeight()].
Coordinates you've got from MotionEvent is screen coordinates with bounds from (0,0) to [displayMetrix.widthPixels, displayMetrix.heightPixels] and they're independat of zoom or camera-to-scene-position.
Solution explanation
For the abscissa (in general x) you must translate screen corrdinates to relevant scene coordinates (bounded with camera width) and sum the up to the camera left corner position (on the scene). In formula it looks like:
x = cameraStartX + screenX * camera.width / display.width
cameraStartX - the camera left corner x position on the scene
screenX - x you get from MotionEvent
camera.width - just getWidth() from your camera method
display.width - display width from DisplayMetrics
For the ordinate (in general y) you have to do the same as for the abscissa but remember that it has a different direction. So the formula will be a bit changed:
y = cameraStartY+(display.heigth-screenY)*camera.height/display.heigth
cameraStartY - camera left corner y position on the scene
screenX - y you get from MotionEvent
camera.height - just getHeight() from your camera method
display.height - display height from DisplayMetrics
The method implementation example
public static void convertScreenToSurfaceCoordinates(
final Camera camera,
final Context context,
final float[] screenCoordinates,
final float[] surfaceCoordinates) {
//you may want to have a separate methods and fields for display metrics
//and no Context as a method param
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
float displayWidth = Math.max(metrics.widthPixels, metrics.heightPixels);
loat displayHeight = Math.min(metrics.widthPixels, metrics.heightPixels);
//abscissa
surfaceCoordinates[Constants.VERTEX_INDEX_X] = camera.getXMin()
+ screenCoordinates[Constants.VERTEX_INDEX_X] * camera.getWidth()
/ displayWidth;
//ordinate
surfaceCoordinates[Constants.VERTEX_INDEX_Y] = camera.getYMin()
+ (displayHeight - screenCoordinates[Constants.VERTEX_INDEX_Y])
* camera.getHeight() / displayHeight;
}

Solution found. Andengine handles touch events in a different way to Android so there is no history of touch coordinates. I simply store them myself in private variables before handling the event:
if (pSceneTouchEvent.getPointerID() == 0) {
pointer0LastX = pSceneTouchEvent.getX();
pointer0LastY = pSceneTouchEvent.getY();
}
if (pSceneTouchEvent.getPointerID() == 1) {
pointer1LastX = pSceneTouchEvent.getX();
pointer1LastY = pSceneTouchEvent.getY();
}
Then I just access these instead of getting the values from the TouchEvent.

You can access the original MotionEvent from the TouchEvent.

Related

Positioning UI or Camera relatively to each other (and screen border)

I'm struggling with this sort of
Screen disposition.
I want to position my Camera so that the world is positionned like in the image with the origin at bottom left. It's easy to set the orthographicSize of the camera as I know how many unit I want vertically. It is also easy to calculate the Y position of the camera as I just want it to be centered vertically. But I cannot find how to compute the X position of the camera to put the origin of the world in this position, no matter what the aspectRatio of the screen is.
It brings me two questions :
How can I calculate the X position of the camera so that the origin of the world is always as the same distance from the screen left and bottom borders ?
Instead of positioning the camera regarding the UI, should I use RenderMode Worldspace for the UI canvas. And if so, how could I manage responsiveness ?
I don't understand the second question, but regarding positioning the Camera on the X axis so that the lower left corner is always at world 0 you could do the following:
var lowerLeftScreen = new Vector3(0, 0, 10);
var pos = transform.position;
var lowerLeftScreenPoint = Camera.main.ScreenToWorldPoint(lowerLeftScreen).x;
if (lowerLeftScreenPoint > 0)
{
pos.x -= lowerLeftScreenPoint;
}
else
{
pos.x += Mathf.Abs(lowerLeftScreenPoint);
}
transform.position = pos;
Debug.Log(Camera.main.ScreenToWorldPoint(lowerLeftScreen));
Not the nicest code, but gets the job done.
Also the Z component in the Vector does not really matter for our orthographic camera.

Math: How to spin a wheel by drag&drop (Canvas, 2D)?

I'm struggling with probably simple math to spin/rotate a wheel using drag&drop.
There is a Radial Layout in a Canvas (Unity UI) and it can already be rotated by setting a property called StartAngle that is in a range from 0-360. In this Radial there are items, so the StartAngle is for the first item and places all the child elements around the layout radius.
I want to implement drag & drop for the items so that you can drag a child around and the Radial will spin accordingly (infinitely).
Right now, I have this as a starting point:
public void OnDrag(PointerEventData eventData)
{
var delta = eventData.delta.x * Time.deltaTime;
var newAngle = radialLayout.StartAngle + delta;
if (newAngle >= 360)
newAngle = newAngle - 360;
else if (newAngle < 0)
newAngle = Mathf.Abs(360 - newAngle);
radialLayout.StartAngle = newAngle;
}
It kind of works but doesn't feel very smooth. This is for mobile/touch, so I want both the X and Y delta of the drag operation to be taken into account. Apparently, the y delta is not considered in my example and I have no idea how to incorporate this correctly. The user might do a linear drag & drop on either axis or he/she might also do like a circular drag movement.
So how can I map mouse movement to a rotation angle from 0-360 so that it feels good?
Edit: Thanks for the help, I did it like this now:
public void OnDrag(PointerEventData eventData)
{
// Note the "Head-Minus-Tale rule for Vector subtraction, see http://www.maths.usyd.edu.au/u/MOW/vectors/vectors-3/v-3-7.html
// vSourceToDestination = vDestination - vSource;
// First, we draw a vector from the center point of the radial to the point where we started dragging
var from = dragStartPoint - (Vector2)radialLayout.transform.position;
// Next, we draw a vector from the center point of the radial to the point we are currently dragging on
var to = eventData.position - (Vector2)radialLayout.transform.position;
// Now, we calculate the angle between these two:
var dragAngle = Vector2.SignedAngle(from, to);
// Lerping makes movement fast at the beginning slow at the end
var lerpedAngle = Mathf.Round(Mathf.LerpAngle(radialLayout.StartAngle, dragAngle, 0.5f));
radialLayout.StartAngle = lerpedAngle;
}
I don't know all of your code and types but I would have an idea. I can't test this right now and can not garant that it even works like this but I hope the idea gets clear.
I would probably rather use something like
// This is the vector from the center of the object to the mouse/touch position
// (in screen pixel space)
var touchDirection = eventData.position - Camera.main.WorldToScreenPoint(transform.position);
// angle between the Up (Y) axis and this touchDirection
// for the angle the length of the up vector doesn't matter so there is
// no need to convert it to pixel space
var targetAngle = Vector2.SignedAngle(Vector2.up, touchDirection);
// since the returned angle might be negative wrap it to get values 0-360
if(targetAngle < 0) targetAngle += 360;
// Now either simply use Lerp
// this would simply interpolate each frame to the middle of both values
// the result is a fast movement at the beginning and a very slow at the end
radialLayout.StartAngle = Mathf.Lerp(radialLayout.StartAngle, targetAngle, 0.5f);
// or maybe use a fixed speed like 30°/second
var difference = targetAngle - radialLayout.StartAngle;
radialLayout.StartAngle += Mathf.Sign(difference) * Mathf.Min(30f * Time.deltaTime, Mathf.Abs(difference));
Typed on smartphone but I hope the idea gets clear

Unity C#: Line renderer from Gameobject (3D) to Canvas (Screen Space - Camera) [duplicate]

I have an image UI in a canvas with Screen Space - Camera render mode. What I like to do is move my LineRenderer to the image vertical position by looping through all the LineRenderer positions and changing its y axis. My problem is I cant get the correct position of the image that the LineRenderer can understand. I've tried using ViewportToWorldPoint and ScreenToWorldPoint but its not the same position.
Vector3 val = Camera.main.ViewportToWorldPoint(new Vector3(image.transform.position.x, image.transform.position.y, Camera.main.nearClipPlane));
for (int i = 0; i < newListOfPoints.Count; i++)
{
line.SetPosition(i, new Vector3(newListOfPoints[i].x, val.y, newListOfPoints[i].z));
}
Screenshot result using Vector3 val = Camera.main.ScreenToWorldPoint(new Vector3(image.transform.localPosition.x, image.transform.localPosition.y, -10));
The green LineRenderer is the result of changing the y position. It should be at the bottom of the square image.
Wow, this was annoying and complicated.
Here's the code I ended up with. The code in your question is the bottom half of the Update() function. The only thing I changed is what was passed into the ScreenToWorldPoint() method. That value is calculated in the upper half of the Update() function.
The RectTransformToScreenSpace() function was adapted from this Unity Answer post1 about getting the screen space coordinates of a RectTransform (which is exactly what we want in order to convert from screen space coordinates back into world space!) The only difference is that I was getting inverse Y values, so I changed from Screen.height - transform.position.y to just transform.position.y which did the trick perfectly.
After that it was just a matter of grabbing that rectangle's lower left corner, making it a Vector3 instead of a Vector2, and passing it back into ScreenToWorldPoint(). The only trick there was because of the perspective camera, I needed to know how far away the line was from the camera originally in order to maintain that same distance (otherwise the line moves up and down the screen faster than the image). For an orthographic camera, this value can be anything.
void Update () {
//the new bits:
float dist = (Camera.main.transform.position - newListOfPoints[0]).magnitude;
Rect r = RectTransformToScreenSpace((RectTransform)image.transform);
Vector3 v3 = new Vector3(r.xMin, r.yMin, dist);
//more or less original code:
Vector3 val = Camera.main.ScreenToWorldPoint(v3);
for(int i = 0; i < newListOfPoints.Count; i++) {
line.SetPosition(i, new Vector3(newListOfPoints[i].x, val.y, newListOfPoints[i].z));
}
}
//helper function:
public static Rect RectTransformToScreenSpace(RectTransform transform) {
Vector2 size = Vector2.Scale(transform.rect.size, transform.lossyScale);
Rect rect = new Rect(transform.position.x, transform.position.y, size.x, size.y);
rect.x -= (transform.pivot.x * size.x);
rect.y -= ((1.0f - transform.pivot.y) * size.y);
return rect;
}
1And finding that post from a generalized search on "how do I get the screen coordinates of a UI object" was not easy. A bunch of other posts came up and had some code, but none of it did what I wanted (including converting screen space coordinates back into world space coordinates of the UI object which was stupid easy and not reversibe, thanks RectTransformUtility!)

Unity - get position of UI Slider Handle

I am working on Unity 4.7 project and need to create shooting on the target. I simulated gunpoint using horizontal and vertical slider moving on the time. When I click the button I need to memorize x and y coordinates of handles and instantiate bullet hole at this point but don't know how to get cords of sliders handle. It is possible to get values but it seems that it doesn't correspond to coordinates. If horizontal slider changes its value for 1, would its handle change x position for 1?
Use this then:
public static Vector3 GetScreenPositionFromWorldPosition(Vector3 targetPosition)
{
Vector3 screenPos = Camera.main.WorldToScreenPoint(targetPosition);
return screenPos;
}
Have the reference to Handles of the horizontal and vertical sliders, and use them like:
Vector3 pos = GetScreenPositionFromWorldPosition(horizontalHandle.transform.position);

Unity 3D get camera rotated angle

In my unity app initially I have set the camera in a certain position. Later I change the camera position dynamically. After doing that I need to find the angle/rotation it rotated and another object needs to be rotated by the same angle but in the opposite direction.
I have two questions.
1.How do I find the angle the camera moved?
2.How do I rotate the game object by the same value but in the opposite direction.
I tried things like target.transform.Rotate( Camera.main.transform.position - cameraNewPos); and also googled a lot but couldn't find the answers.
You can always compare the current transform.rotation to its original value before you made the change to see the different. Then, apply the same change (in reverse) to your other object.
On camera:
var delta = transform.rotation - _originalRotation;
On that Object:
transform.rotation = transform.rotation - delta;
If your camera is at position P1, and orientation Q1 at start, and P2, Q2, after you moved it, you can find the translation T and rotation R that goes from 1 to 2 using (using transform.rotation and transform.position, which represent the world transform of you object, independent of its hierarchy) :
R = Q1.Inverse() * Q2
T = P2 - P1
Note that you can't substract quaternions.
Then you can simply move your other object accordingly.
Obj.position = initialCameraPosition - T
Obj.rotation = initialCameraRotation * R (or R.Inverse() if you want to mirror the rotation as well)
So:
If you want to get the amount of rotation applied from a time to a time, you could just save the data from the begin and then substract the end rotation - begin rotation.
Vector3 iRotation;
Vector3 amountRot;
void Start() { // for example
iRotation = obj.transform.rotation.eulerAngles;
}
void Update() { // at some point you want
if (sth) amountRot = obj.transform.rotation.eulerAngles - iRotation;
obj2.transform.rotation = Quaternion.Euler(-obj.transform.rotation.eulerAngles); // rotate opposite