Matching GUI to 3D game world using Rendercam

I’m trying to match a GUI node position and width to a gameworld model using Rendercam.


How would I go about achieving this?

This is my result so far:

Script:

function init(self)

	timer.delay(0.1, false, function() -- Wait at least 10 frames so Rendercam has is updated
		
		-- Set background colour
		msg.post("@render:", "clear_color", { color = vmath.vector4(1, 1, 1, 1) } )

		-- Create cube
		local MODEL_SIZE = vmath.vector3(1,1,1)
		local scale = 200
		local scaled_model_size = MODEL_SIZE * scale
		local pos = vmath.vector3(0,-200,0)
		local cube = factory.create("go#cube_factory", pos, nil, nil, vmath.vector3(scale))
		local model = msg.url(nil, cube, "model")
		local light_pos = vmath.vector4( pos.x,pos.y-1000,pos.z+1500,1)  
		go.set(model,"light", light_pos)

		-- Create GUI node to match
		local screen_pos_x, screen_pos_y = rendercam.world_to_screen(pos, gui.ADJUST_FIT, true)
		local gui_pos_x,gui_pos_y = rendercam.screen_to_gui(screen_pos_x,screen_pos_y, gui.ADJUST_FIT, false)
		local gui_size_x, gui_size_y = rendercam.screen_to_gui(scaled_model_size.x, scaled_model_size.y, gui.ADJUST_FIT, true)		
		local gui_pos = vmath.vector3(gui_pos_x,gui_pos_y,0)
		local gui_size = vmath.vector3(gui_size_x,10,0)
		local node_color = vmath.vector4(0.9,0.2,0.1,1)
		msg.post("go#gui", "new_box_node", {pos=gui_pos, size=gui_size, color=node_color} )
		
	end)

end

Gui script:

function on_message(self, message_id, message, sender)

	if message_id == hash("new_box_node") then
		local node = gui.new_box_node(message.pos, message.size)
		gui.set_color(node, message.color)
	end
	
end

Project zipped up: Archive.zip (123.3 KB)

Hmm, this may be due to an issue that Pkeod found a few months ago, and I haven’t either changed the implementation or documented the weirdness for. https://github.com/rgrams/rendercam/issues/39#issuecomment-592519011

rendercam.world_to_screen

  1. If you do not give it an adjust mode, it converts world coordinates to screen coordinates.
  2. If you DO give it an adjust mode, it converts world coordinates to gui coordinates.

I will try to actually take a look at the project later today.

4 Likes

Ah, that explains the positioning! This line on its own works:

	local gui_pos = rendercam.world_to_screen(pos, gui.ADJUST_FIT, false)

I haven’t been able to get the size to work yet. New project file: Archive.zip (202.8 KB)

I took a look. Your cube model is actually 2x2x2 units, from -1 to 1 in all dimensions (take a look at the rulers on the viewport). The rendercam function is giving the correct result, it’s exactly half as much as you expected it to be. :slight_smile:

However…it doesn’t work after you change the window size, so something’s wrong with Rendercam too. :frowning:

[Edit1] Oh, looking some more…you’re not transforming the cube size from world to GUI coordinates, just relying on the 1:1 ortho camera, so it’s not a bug. But apparently there’s no “is a delta” option for world_to_screen…what kind of bozo wrote this library…

[Edit2] Hmm, maybe there is a bug. I need to set up a better test for this.

[Edit3] OK, it works. I should add a delta parameter to world_to_screen so you can transform distances, but until I do that you can just transform two points and get the difference.

Archive3.zip (158.1 KB) - (Click to alternately create/destroy the cube & node.)

2 Likes

DOH! You’re right, of course.

Ha ha ha!

That’s genius. Thanks so much for having a look at this, AND for actually fixing it.

Edit: For the lazy, the juicy code:

   -- Create cube
    local MODEL_SIZE = vmath.vector3(2)
    local scale = 200
    local scaled_model_size = MODEL_SIZE * scale
    local pos = vmath.vector3(0,0,0)
    local cube = factory.create("go#cube_factory", pos, nil, nil, vmath.vector3(scale))
    cube_id = cube
    local model = msg.url(nil, cube, "model")
    local light_pos = vmath.vector4( pos.x,pos.y-1000,pos.z+1500,1)
    go.set(model,"light", light_pos)

    -- Create GUI node to match

    -- Convert cube world pos to gui pos.
    local gui_pos = rendercam.world_to_screen(pos, gui.ADJUST_FIT, false)
    -- Convert cube world size to GUI size.
    local x1, y1 = rendercam.world_to_screen(vmath.vector3(0, 0, 0), nil, true)
    local x2, y2 = rendercam.world_to_screen(vmath.vector3(scaled_model_size.x, scaled_model_size.y, 0), nil, true)
    local gui_w, gui_h = x2 - x1, y2 - y1
    gui_w, gui_h = rendercam.screen_to_gui(gui_w, gui_h, gui.ADJUST_FIT, true)

    local gui_size = vmath.vector3(gui_w,10,0)
    local node_color = vmath.vector4(0.9,0.2,0.1,1)
    msg.post("go#gui", "new_box_node", {pos=gui_pos, size=gui_size, color=node_color} )
2 Likes

Follow up question: How would I calculate the position if the camera is not at 0,0,0? Would I have to convert its position to GUI coordinates and offset the GUI node?

You’re already doing that with the world_to_screen transform. You can move or rotate the camera however you want.

1 Like

Strange. It might not work because the frame delay. I’ll investigate.

Ah, yes, that would probably mess it up if you move the camera and do the position transform in the same frame. Ugh, I don’t think there’s any universal solution for that one. You could theoretically move the camera and transform positions multiple times per frame.

Confirmed - waiting a frame solves the issue. Arigato.