15

The Problem at hand:

Simplified

Given an UnityEngine.Ui.Image How does one find the X,Y position of a normalised offset (like 0.4, 0.3 from the top left) inside that image in ScreenSpace units like 400,300

I guess I need to find the top left ScreenSpace value and then knowing the rendered total size of the image scale the normalised offsets by the actual size ratio expressed in pixels.

Figure 1: Figure 1:

Figure 2 shows the normalisedOffsets that are to be used

Figure 2: An example stored Rect "offset"

So, in precis, I need to find the offset in ScreenSpace pixels of the topLeft of the Rect I have stored against the image.

I recognise it is probably a combination of Camera.main.ViewportToWorldPoint() and some reference to the bounds, possibly scaling that by backgroundImage.sprite.pixelsPerUnit?

Struggling to visualise how to exactly get this done.

thanks

Community
  • 1
  • 1
twobob
  • 354
  • 8
  • 22
  • 1
    Is [`TransformPoint()`](https://docs.unity3d.com/ScriptReference/Transform.TransformPoint.html) what you're looking for? – Draco18s no longer trusts SE Jul 11 '18 at 22:15
  • @draco18s um. maybe? I added some code to show what I tried so far. – twobob Jul 11 '18 at 22:44
  • 2
    Looking at the code you posted, it will never work, because you're taking a transformed point and adding/subtracting *non-transformed* values. Additionally, `ViewportToWorld` doesn't take into account canvases *in world space*: just because the center of your camera is at `(x,y,z)` does not mean that the center of a canvas is at `(x,y,z)` (it is, afterall, in world space with its own coordinates independent from the camera). However, [this old answer](https://stackoverflow.com/a/48134642/1663383) may help. – Draco18s no longer trusts SE Jul 11 '18 at 23:15
  • 1
    I looked though https://pastebin.com/wHHJwVui and https://stackoverflow.com/questions/48133920/how-to-convert-new-canvas-ui-image-position-to-linerenderer-position/48134642#48134642 I have tested given this info and the only value I do have right so far seems to be the depth from the camera to place my marker object. Perhaps if I simplify/redefine the problem this will seem easier. – twobob Jul 12 '18 at 00:23
  • 2
    Re simplified: what does `(0.4, 0.3)` *mean?* What units are these in? In what space? World space? How do you account for the screen aspect ratio? Field of view? Camera rotation? Your images seem to indicate you wish to draw a square around faces (or some other feature), but that feature exists in pixel space: you'd have to convert *those* units over *into* world space units, not the other way around. – Draco18s no longer trusts SE Jul 12 '18 at 03:44
  • they are a normalised offset (like 0.4, 0.3 from the top left) in the original image - I would inded have to convert them to the actual size – twobob Jul 13 '18 at 06:30
  • so.. 40% and 30%... – twobob Jul 13 '18 at 09:49
  • 1
    You need to convert those values into pixel values using the size of the image in screenspace and add them to the top left corner *then* convert to worldspace. – Draco18s no longer trusts SE Jul 13 '18 at 15:47
  • I get that. "I guess I need to find the top left ScreenSpace value and then knowing the rendered total size of the image scale the normalised offsets by the actual size ratio expressed in pixels." It's in the question. I am asking how one might go about that, since my first actual attempts, (documented in the previous question, that I simplified because it was obviously TLDR;) gave incorrect values. I don't see how I can describe it more clearly. I understand the theory. not getting the right numbers. – twobob Jul 15 '18 at 15:16
  • 2
    Get the [rect transform's width](https://docs.unity3d.com/ScriptReference/RectTransform-rect.html), multiply by your `(0.4, 0.3)` value, add to the top left corner, convert to world space. – Draco18s no longer trusts SE Jul 15 '18 at 15:31
  • Thanks for that, however it returns exactly the same value for all images regardless of geometry. I guess I need to jump to the image itself then to determine that stuff – twobob Jul 16 '18 at 01:26
  • 1
    Offhand all I can suggest is to play with it. – Draco18s no longer trusts SE Jul 16 '18 at 01:42
  • yeah. thanks anyway – twobob Jul 16 '18 at 01:46
  • 2
    if you want to use this with any camera angle, i think it will not be as simple as "knowing the rendered total size of the image". i would approach this using the equation for the 3D plane where your image lies on, then calculate the offset on that plane and then use the camera's View and Projection matrices to transform that result to screen coordinates. i hope this helps. – Jinjinov Jul 31 '18 at 09:39
  • 1
    Have you tried Camera.main.ScreenToWorldPoint? – Piotr Krankiewicz Aug 01 '18 at 22:10
  • in the end I simply calculated who was closest to the left for my use case and sorted the name labels that way, since in LTR reading countries that was the convention anyway. – twobob May 03 '20 at 01:41

1 Answers1

0

I guess you don't have any scale or rotation in the image parents and the position Y is 0.

First you can get the position of the upper left corner of your image with rectTransform.GetWorldCorners():

    //Upper left corner
    Vector3[] v = new Vector3[4];
    image.rectTransform.GetWorldCorners(v);
    var recPos = v[1];

Then you have to transform your normalized offset to a world space offset by a ratio between your image size and your rect size and add the position of the top left corner:

    var recWidth = image.rectTransform.sizeDelta.x;
    var imgWidth = image.sprite.texture.width;
    var realOffsetX = offsetX * (recWidth / imgWidth);
    var realPosX = recPos.x + realOffsetX;

(It is the same formula for the Y coordinate but you have to subtract by your ratio realOffsetY because the offset is calculated from the top left corner)

Here is the full method:

private Vector3 GetPositionOffset(Image image, float offsetX, float offsetY)
{
    //Upper left corner
    Vector3[] v = new Vector3[4];
    image.rectTransform.GetWorldCorners(v);
    var recPos = v[1];

    //X coordinate
    var recWidth = image.rectTransform.sizeDelta.x;
    var imgWidth = image.sprite.texture.width;
    var realOffsetX = offsetX * (recWidth / imgWidth);
    var realPosX = recPos.x + realOffsetX;

    //Y coordinate
    var recHeight = image.rectTransform.sizeDelta.y;
    var imgHeight = image.sprite.texture.height;
    var realOffsetY = offsetY * (recWidth / imgWidth);
    var realPosY = recPos.y - realOffsetY;

    //Position
    return new Vector3(realPosX, realPosY, image.transform.position.z);
}

Then if you want this World space to screen space just use the camera method:

camera.WorldToScreenPoint(positionOffset);
CJ Dennis
  • 4,226
  • 2
  • 40
  • 69
Ludovic Feltz
  • 11,416
  • 4
  • 47
  • 63
  • Let me check that. appreciate the assist – twobob Aug 03 '18 at 02:21
  • So If I pass in 0,0 as my offset. and then run the Camera.Main.WorldToScreenPoint(positionOffset); I do not get the expected default output of top left? I Must be missing something – twobob Aug 03 '18 at 09:02
  • the resulting location is in fact off the screen by quite a margin, did you have a worked test – twobob Aug 03 '18 at 09:07
  • @twobob That is weird, if you pass 0,0 you should have the position coming from GetWorldCorners() and it must be the position of the top left corner of your image. Do you have any scale/rotation on your parents object? – Ludovic Feltz Aug 03 '18 at 09:57
  • I followed through the maths line by line in Visual Studio and am unclear where I could have gone wrong. – twobob Aug 03 '18 at 19:24
  • I have no scale or rotation. I get 46,766 for my first values. Do you have a worked example? – twobob Aug 04 '18 at 11:51