How to use convertPositionToNode/World/AR with normalized positions?

If you’re using regular positions (ie node->setPosition()), the conversions work well, but if the original’s parent is using a normalized position, and the destination is not normalized, the conversions don’t work.

If the node who’s position I’m trying to into, touch_to_close_overlay_layout, is non-normalized to the center of the map with anchor 0.5, 0.5, (its size is 9999x9999), it looks like this, which is as intended:

but if I change the touch_to_close_layout to normalized position, setPositionNormalized(0.5, 0.5), it’ll get out of place. notice the two bright buttons are missing on the right:

so if I change the size of the grey see-through layout from 9999 to 1000, you’ll see how it changes. again, non-normalized and positioned wherever on screen, it doesn’t matter, it works as intended:

but if I keep the size at 1000, and instead normalize the position of the overlay to 0.5 again, the buttons are gone:


Alright so thats roughly what the issue looks like. But if I use the map->converToWorldSpace then touch_to_close_layout->convertToNodeSpaceAR, it gets close, but it seems to be scaling out, rather than absolute positioning:

If I shrink the layout from 1000 to 500, the positions get even further out of whack, so it does seem to be some sort of scaling issue


The touch_to_close_overlay_layout is the grey see through layout, location_btn_origin is the bottom left corner of the bounding box of the ‘Add Forest’ button, added to the map. The dropdown_listview is a child of the touch_to_close_overlay_layout, and contains two buttons within it, the second ‘Add Forest’ and ‘Gather’ buttons. I’ve tried using all of these as the position of the dropdown_listview, but nothing seemed to position it correctly, like it did when the touch_to_close_overlay_layout is non-normalized positioned.

Both the first Add Forest button and touch_to_close_overlay are added to the same parent.

auto btn_world_space = location_btn_parent->convertToWorldSpace(location_btn_origin);
auto btn_world_space_AR = location_btn_parent->convertToWorldSpaceAR(location_btn_origin);

auto btn_local_overlay_pos = touch_to_close_overlay_layout->convertToNodeSpace(btn_world_space);
auto btn_local_overlay_pos_AR_AR = touch_to_close_overlay_layout->convertToNodeSpaceAR(btn_world_space_AR);
auto btn_local_overlay_pos_AR = touch_to_close_overlay_layout->convertToNodeSpaceAR(btn_world_space);

dropdown_listview->setPosition(
    btn_local_overlay_pos
);

tl;dr if I’m scaling the position of one of the nodes involved here, how can I easily convert the positions of one of the nodes to another? Is the problem that I’m mixing and matching normalized and non-normalized positions?

edit: looking through Node::getNodeToParentTransform, it looks at _position a few times, and setting setPositionNormalized doesnt seem to update _position directly, so maybe that’s it. Mixing is the problem? But in Node::visit, processParentFlags is called, which should update _position, so I’m not sure still.

If I create the node and immediately use it, before visit is called, that would mean that the _position isn’t updated. I’ll try that.

1 Like

Yeah that was it, I’m on cocos2dx 3.14, so I don’t know if this has been resolved, but it looks like its an edge case where Node::_position isn’t getting updated until Node::visit is called, calling Node::processParentFlags, updating Node::_position. The reason it’s delayed is because cocos needs a parent in order for it to have actual _position data, so it leaves it unset for as long as possible.

If you use setPositionedNormalized, you need to either call setPosition and do the math manually, then swap it back to normalized, or hackily delay the positioning a frame until visit gets called once.

Writing all this out helped solve it though, so thanks for rubber ducking with me.

edit: current v3 branch still looks like it works that way, where it delays updating _position until ::visit is called, https://github.com/cocos2d/cocos2d-x/blob/9f424e214f0ed08589ce57a93d1a29a9eaf88eb7/cocos/2d/CCNode.cpp#L582

1 Like

When are you trying to use the convert methods? I thought these are only really used in input handing (touch events, etc) or they’re called internally when someone requests specific screen/world transforms and therefore visit should’ve been already called on every node. ??

You’re not doing anything wrong, but maybe there’s an alternative way that works better with normalizedPosition.

This is the only time I have ever used them too.

@stevetranby @slackmoehrle

I’ve typically used the conversions for that global to local space conversion, since it’ll run all the way up the graph to get the ‘actual’ coords for it. I don’t know if that’s what you’re supposed to do though.

When you clicked the Add Forest button, it added a few buttons to the screen, to work like a rough dropdown list.

In order for it to work, I added a semi-transparent fullscreen layout that you touched to close the dropdown, then added the two buttons as children to that layout.

The issue I was having was that the buttons needed to know where on the fullscreen layout they needed to go, in order to look like they were positioned below the Add Forest button. So I got the world coords for the initial button, then tried to convert it to the local coords on the layout.

I could have just added the new second and third button as children of the map and hid and shown them, but I didn’t want to mess around with Zorders or anything for the touch layout.

This is what I built with it

Ah interesting, again if it’s working and you understand it, just keep using what you have. In your exact case manual calculating position is probably required.


Since you’re looking to position the sub-menu buttons directly beneath the button that was clicked you could try to instead add the dark background as a child to parent and then add the buttons (or ListView w/buttons) to that same parent, instead of adding the buttons of the sub-menu as child of the dark background.

This may not solve it entirely, but the [Add Plains] button’s screen coord is known (regardless of normalized positioning of [Add Plains] button or its parents) as you already have.

Therefore you could just set the position of the sub-menu root node (either the ListView or just an empty Node used for positioning) directly as screen coordinates (with an offset to move it below the [Add Plains] button).

I’m assuming once this sub-menu is open it should close before the game continues?
(ie: it’s a temporary ‘context’ menu)

// one option is get current scene
touch_to_close_overlay_layout->setNormalizedPosition(positionToCenterOnMap);
currentScene->addChild(touch_to_close_overlay_layout);

// could use the AR version, depends your exact needs 
// (prob use only one or the other, not mix)
screenCoord = currentScene->convertToNodeSpace(btn_world_space);
subMenu->setPosition(screenCoord);
currentScene->addChild(subMenu);

Here’s quick parent hierarchy that hopefully makes my thoughts clear(er)??

Scene
-> Game Map
-----> ... parent hierarchy ...
-----------> [Add Plains] Button
-> UI
-> Dark Background (with touch to close event handling)
-> Sub-Menu
----> [Add Forest], [Gather] buttons

Instead of trying to add it into a parent of the dark background and any other parent hierarchy.

Scene
-> Game Map
-----> ... parent hierarchy ...
-----------> [Add Plains] Button
-> UI

--> ... some parent node either under UI or Scene
------> Dark Background
---------> Sub-Menu
------------> [Add Forest], [Gather] buttons
2 Likes

If I’m understanding you correctly, that first tree is similar how I’m currently doing it, where the overlay is added directly to the running scene, then the listview with the two extra buttons on top of it, positioned where it needs to be.

Scene
-> Map (and rest of ui)
   \-> [Add Plains ] button
-> Dark background    // you are suggesting this, and 
   \-> Sub-menu       // sub-menu are siblings instead of parent-child, I think
      \-> [Hunt]
      \-> [Gather]

I believe you’re suggesting that I skip positioning the sub menu relative to the dark background, and just add it as a sibling, skipping the repositioning step to local of the dark background. That makes a lot of sense, I should have thought of that! It would look like this,

Scene
-> Map (and rest of ui)
   \-> [Add Plains ] button
-> Dark background   // child of Scene
-> Sub-menu          // also child of Scene now, instead of child of dark background
    \-> [Hunt]
    \-> [Gather]

That way you’re guaranteed that the buttons are above the layout, and the layout is above the rest of the UI, thanks!