godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.14k stars 93 forks source link

Add a transform parameter to `CanvasItem.draw_texture()` #3038

Open iRadEntertainment opened 3 years ago

iRadEntertainment commented 3 years ago

Describe the project you are working on

I'm working on a Plug-in that let you "paint" Sprites within the editor. It shows a preview of it before you click and add a sprite node on that location. It could be useful to decorate a scene with multiple sprites without having to go and pull it one by one if you have many. The project can be found on GitHub here.

Describe the problem or limitation you are having in your project

I show the preview of the future created Sprite on the overlay in func forward_canvas_draw_over_viewport(overlay) in an EditorPlugin script. Using the suggested method of draw_set_transform or draw_set_transform_matrix before calling draw_texture() (like suggested in this closed issue) also modifies the rulers and other things drawn on the editor main viewport. So this way becomes unusable to display a preview of a rotated sprite.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

This request has been seen several times and, despite the possible use of the draw_set_transform functionalities I think will simplify the process substantially if you could be allowed to pass as an optional arg a Transform2D in the draw_texture. This would solve the problem of manipulating a Texture.Transform2D instead of a whole local set_transform for that specific class.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

it could be: void draw_texture(texture: Texture, position: Vector2, transform : Transform2D = Transform2D.IDENTITY, modulate: Color = Color( 1, 1, 1, 1 ), normal_map: Texture = null)

instead of: void draw_texture(texture: Texture, position: Vector2, modulate: Color = Color( 1, 1, 1, 1 ), normal_map: Texture = null)

If this enhancement will not be used often, can it be worked around with a few lines of script?

In my example I couldn't find a way to use the workaround. For other uses it could be done with draw_set_transform or draw_set_transform_matrix

Is there a reason why this should be core and not an add-on in the asset library?

I don't see it useful in the add-on library

Xrayez commented 3 years ago

Using the suggested method of draw_set_transform or draw_set_transform_matrix before calling draw_texture() (like suggested in this closed issue) also modifies the rulers and other things drawn on the editor main viewport. So this way becomes unusable to display a preview of a rotated sprite.

If this affects other editor overlay drawing, perhaps all you need to do is to reset the transform back, something like this:

func _draw():
    draw_set_transform(transform)
    draw_texture(...)
    draw_set_transform(Transform2D())

If it doesn't resolve it, then perhaps overlay drawing should be fixed instead.

iRadEntertainment commented 3 years ago

Yes I did that, but for some reason it didn't work. I can't pinpoint exactely why. When I'll be back to the computer I'll post an example project.

Anyway, since the many questions and requests on this topic I still think that many Godot users will benefit from some solution for this matter.

KoBeWi commented 3 years ago

Maybe what you need is

var trans = draw_get_matrix()
draw_set_matix(my_matrix)
stuff...
draw_set_matrix(trans)

except draw_get_matrix() doesn't exist.

Xrayez commented 3 years ago

forward_canvas_draw_over_viewport(overlay) could pass currently applied transform as well.

iRadEntertainment commented 3 years ago

UPDATE: I was mistaken and this actually works as expected:

If this affects other editor overlay drawing, perhaps all you need to do is to reset the transform back, something like this:

func _draw():
    draw_set_transform(transform)
    draw_texture(...)
    draw_set_transform(Transform2D())

If it doesn't resolve it, then perhaps overlay drawing should be fixed instead.

I don't know what I did wrong in the past experiments with this method It is still a bit unexpected that the draw_set_transform() affects the rulers of the view: image If this behaviour is not the intended one I may open an issue on that (Godot v3.3.2), but I would say that, in almost every case, you would very like reset the transform at the end of the drawing session.

Here is a script to test things out:

tool
extends EditorPlugin

#- Icon to be draw on the overlay
const ico : Texture = preload("res://icon.png")
var tex_size : Vector2 = ico.get_size()
var rot_angle = 0

#- Reference an object in the scene
var ref_obj

#--- Enter and exit tree unused in this example.
func _enter_tree(): pass
func _exit_tree(): pass

#--- Handles(object).
# It needs to return true in order to make use of
# forward_canvas_gui_input(event) for grabbing the inputs
# and forward_canvas_draw_over_viewport(overlay) to draw on the overlay.
func handles(object):
    ref_obj = object
    tex_size = ico.get_size()
    return (object)

var ctrl_pressed := false
func forward_canvas_gui_input(event):
    #--- Update the overlays at any event.
    # update_overlays() calls forward_canvas_draw_over_viewport(overlay)
    update_overlays()

    #--- Grab the inputs on the UI
    # Pressing CTRL and scrolling the wheel changes the rot_angle.
    # That is used in forward_canvas_draw_over_viewport(overlay)
    # to draw the texture on the overlay
    if event is InputEventKey:
        if event.scancode == KEY_CONTROL:
            ctrl_pressed = event.is_pressed()

    if event is InputEventMouseButton and event.is_pressed() and ctrl_pressed:
        if event.button_index == BUTTON_WHEEL_DOWN:
            rot_angle += PI/60
            return true
        elif event.button_index == BUTTON_WHEEL_UP:
            rot_angle -= PI/60
            return true
        elif event.button_index == BUTTON_MIDDLE:
            rot_angle = 0

# This func is called at every update_overlays() call
func forward_canvas_draw_over_viewport(overlay):
    #--- Reference viewport transform (for the view zoom) and mouse position
    # on the canvas.
    # At the moment thi is the easiest way to get the viewport transform.
    # You need to reference a CanvasItem in the scene and get its viewport transform.
    var view_transform = ref_obj.get_viewport_transform()

    var mouse_loc_pos = overlay.get_local_mouse_position()
    var mouse_glb_pos = overlay.get_global_mouse_position()

    #--- get the Texture size and scale it by the viewport zoom level
    var scaled_tex = tex_size * view_transform.get_scale()
    var text_rect = Rect2(-scaled_tex/2, scaled_tex)

    #--- Set the transform for the draw session, then draw the sprite
    # overlay.draw_set_transform(position, rotation, scale)
    overlay.draw_set_transform(mouse_loc_pos, rot_angle, Vector2.ONE)
    overlay.draw_texture_rect(ico, text_rect, false, Color(1,1,1,0.3))

    #--- Reset the draw transform for the overlay to default
    # comment the next line out to see how the overlay draw_set_transform()
    # affects the rulers in the main viewport
    overlay.draw_set_transform(Vector2.ZERO, 0, Vector2.ONE)

To use it:

Xrayez commented 3 years ago

Yeah, in that case, Godot could just reset transform on C++ level. To be extra safe, it could save current transform prior to calling forward_canvas_draw_over_viewport() callback, and resetting it after the callback is finished. I'm not sure how this works exactly, but I think it's something that is more or less straightforward to fix once you know the internal workings of it, @KoBeWi may be able to fix this in fact.

KoBeWi commented 3 years ago

Ok opened the PR to prevent breaking editor: https://github.com/godotengine/godot/pull/50873

draw_texture() already supports position, so if you want rotating the texture, #2624 would be more suited.

iRadEntertainment commented 3 years ago

Ok opened the PR to prevent breaking editor: godotengine/godot#50873

Nice!

draw_texture() already supports position, so if you want rotating the texture, #2624 would be more suited.

I find draw_texture_rect() more flexible since you can define a Rect. Using it I would like to suggest to keep the Rect which gives us position and size and add an extra Transform that will help with the rotation around a pivot point.

Example:

var ico = preload("res://icon.png")
func _draw():
    #--- define the Rect
    var size : Vector2 = _size
    var offset : Vector2 = _offset
    var tex_rect := Rect(offset, size)

    var pos : Vector2 = _pos
    var rot : float = _rot
    var tex_transform = Transform2D(rot, pos)
    draw_texture_rect(ico, tex_rect, tex_transform)

    # actual args
    # draw_texture_rect(texture, rect, tile, modulate, transpose, normal_map)
    # wanted args
    # draw_texture_rect(texture, rect, transform2d, tile, modulate, transpose, normal_map)

This way it would be possible to rotate the texture around the offset