camera.worldToScreen issues

I’m trying to position a Text Element that is a child of a 2D Screen element on top of an Entity that’s part of the 3D world space.

The Camera docs mention this method:

https://developer.playcanvas.com/en/api/pc.CameraComponent.html#worldToScreen

According to its description “Convert a point from 3D world space to 2D screen space.” I would expect to give the function a Vector3 representing the world position in 3D space, and receive in return a Vector3 representing the same position when projected into the 2D Screen space.

However, this does not seem to be the case.

Am I missing something, or did I get it wrong?

Hi @mariogarranz,

Since the element is a child of a 2D Screen it has a different translation mode, so using a 3D world space position won’t work in this case.

Take a look at the following post, it provides a way to do that:

1 Like

Thanks a lot for the quick reply, @Leonidas.

I checked the responses on that thread and it seems that the solution suggested in that case was to move the Text Element outside of the 2D Screen and into the 3D space?

I know that would work, but it means:

  • I have to rotate the Text Element to always face the camera
  • It’s not in the same hierarchy as the other UI elements, which makes it less clean

So, even though that option would work (and it is a valid solution) I’d really prefer to have a way to determine the projection of a 3D entity into the 2D Screen space, but my problem is I’m not sure how the engine is doing the scaling calculations to determine the 2D space.

Yes, it makes sense, it could be useful to have a method that could translate the position of an Element in a 2D Screen based on the world space pos.

Not sure if it’s an easy method to put in place given the number of translations happening but try posting a feature request on the engine repo.

1 Like

Periodically encountered a similar need and finally decided to add my own method to camera component.

This works for any hierarchy of screen elements (sizes, anchors and pivots of parent elements or element itself don’t matter) and has been tested at different pixel ratios and screen resolutions.

Hope this helps someone.

/**@param {pc.Vec3} worldPos
 * @param {pc.Vec3} screenPos
*/
pc.CameraComponent.prototype.worldToScreenSpace = function(worldPos, screenPos = undefined){
    screenPos = this.worldToScreen(worldPos, screenPos);
    var width = this.system.app.graphicsDevice.canvas.clientWidth;
    var height = this.system.app.graphicsDevice.canvas.clientHeight;
    screenPos.x = (screenPos.x / width) * 2 - 1; // 0 to 1 range value map to -1 to 1 range
    screenPos.y = (1 - (screenPos.y / height)) * 2 - 1; // 1 to 0 range value map to -1 to 1 range
    screenPos.z = 0;
    return screenPos;
};

Usage example

/**@type {pc.CameraComponent} */
var camera; // camera reference
/**@type {pc.Entity} */
var worldTargetEntity; // reference to target world entity
/**@type {pc.Entity} */
var screenElementEntity; // reference to screen element entity you want to move

var screenPos = camera.worldToScreenSpace(worldTargetEntity.getPosition());
// // or if you have some 3D vector to receive screen coordinate result to avoid creating a new vector by worldToScreen method inside
// var exampleVec3 = new Vec3();
// var screenPos = camera.worldToScreenSpace(worldTargetEntity.getPosition(), exampleVec3);
// // And this does not create a new vector, it is simply more convenient for naming, so that in further operations the name is not some exampleVec3 or globalVec3, although it is a reference to that vector
screenElementEntity.setPosition(screenPos);
1 Like