• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!
  • Hello [name]! Thanks for joining the GMC. Before making any posts in the Tech Support forum, can we suggest you read the forum rules? These are simple guidelines that we ask you to follow so that you can get the best help possible for your issue.

Legacy GM draw_sprite_pos() distorts the display

Status
Not open for further replies.

Michael

Member
Concerning GMS 1.4.1772 (and probably GMS 2.1 too):

A simple rectangular image should be displayed transformed with draw_sprite_pos() within an objects draw()-event:

Code:
//this draws sprite with original shape and size (200 x 200 pixel):
draw_sprite_pos(sprite0, 0, 100, 100,
                            100+200, 100,
                            100+200, 100+200,
                            100, 100 +200, 1);
                           
//this draws sprite first time transformed:
draw_sprite_pos(sprite0, 0, 400, 100,
                            400+200, 50,
                            400+200, 150+200,
                            400, 100 +200, 1);
                           
//this draws sprite second time transformed:                          
draw_sprite_pos(sprite0, 0, 700, 100,
                            700+200, 100,
                            750+200, 100+200,
                            650, 100 +200, 1);
The outcome should look like this:
draw_sprite_pos_correct.png

What GMS displays is that:
draw_sprite_pos.png

I reported it as a bug to YoYo.
 
R

renex

Guest
That's not a bug, that's just how texture mapping works. If you want actual 3d transformation then you need a 3d projection, or a method which uses a more detailed distortion mesh.
 

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
BTW, is the function usable for anything in current state (without correction) - does anyone use it? With correction it would be really useful.
It works perfectly well for any non-perspective transformations - such as mapping textures to isometric walls, floors, and slopes, for instance.
 

Michael

Member
OK, at first I wanted to let the thing rest, but the further replies of my thread requires another statement.

that's just how texture mapping works.
There is nothing like a "working texture mapping" by itself. It depends totally on the kind of the triangulation one uses for a given area.

YoYo used the absolute simpliest triangulation for an image-quadrileteral which is:
triangle2.png
You can comprehend this easily through code:
Code:
//THIS IS YOYO's "SOLUTION":
//this draws sprite with original shape and size (200 x 200 pixel):
draw_set_colour(c_white);
var tex = sprite_get_texture(sprite0, 0);
draw_primitive_begin_texture(pr_trianglelist, tex);
draw_vertex_texture_colour(100, 100, 0, 0, c_white, 1);
draw_vertex_texture_colour(300, 300, 1, 1, c_white, 1);
draw_vertex_texture_colour(100, 300, 0, 1, c_white, 1);

draw_vertex_texture_colour(100, 100, 0, 0, c_white, 1);
draw_vertex_texture_colour(300, 100, 1, 0, c_white, 1);
draw_vertex_texture_colour(300, 300, 1, 1, c_white, 1);

//this draws sprite first time transformed:
draw_vertex_texture_colour(400, 100, 0, 0, c_white, 1);
draw_vertex_texture_colour(600, 350, 1, 1, c_white, 1);
draw_vertex_texture_colour(400, 300, 0, 1, c_white, 1);

draw_vertex_texture_colour(400, 100, 0, 0, c_white, 1);
draw_vertex_texture_colour(600, 50, 1, 0, c_white, 1);
draw_vertex_texture_colour(600, 350, 1, 1, c_white, 1);

//this draws sprite second time transformed:
draw_vertex_texture_colour(700, 100, 0, 0, c_white, 1);
draw_vertex_texture_colour(900, 300, 1, 1, c_white, 1);
draw_vertex_texture_colour(700, 300, 0, 1, c_white, 1);

draw_vertex_texture_colour(700, 100, 0, 0, c_white, 1);
draw_vertex_texture_colour(900, 100, 1, 0, c_white, 1);
draw_vertex_texture_colour(900, 300, 1, 1, c_white, 1);

draw_primitive_end()
;
The output of this code is exactly, what draw_sprite_pos() delivers (unimportant: it is here more blurred, because I used a small texture of 64x64 drawn at 200x200):
vertex_YOYO.png

you need a 3d projection, or a method which uses a more detailed distortion mesh.
The topic is about 2D-drawing-functions, so the hint to 3D projection is pointless.

Already the next more complicated triangulation than the one used by YoYo fulfills the task of a correct distorted display perfectly, where the u,v-values in the center must be simply .5, .5:
triangle4.png
I comprehended that by code too, if one wishes i could post the code (but it will put economic damage to Dmi7ri's sellings of his "Draw Sprite Pos Fixed"-offer).

That's not a bug
It is a bug.

In the manual YoYo tells: "Draw_sprite_pos(): With this function you can draw a sprite distorted over the area defined by the four corner coordinates.".

This implies, one can use it for perspectival or 3D-effects, which one can not, as the function in it's current state is only good for displaying uniformed colored rectangles transformed, any pictorially content (depending of the kind of distortion) will be displayed falsely distorted.

Did you really make a topic and submit the support ticket before consulting the manual on the function in question?
Of cause I consulted the manual before submitting the bug. Referring to my remarks above, YoYo should remove the warning about false distorted sprites due to the way that a sprite is constructed from a quad of primitives. It might make software engineers of other companies giggling.
 
Last edited:

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
YoYo used the absolute simpliest triangulation for an image-quadrileteral which is:
The purpose of the function is to allow fast affine transformations without touching matrixes and/or breaking GPU batch, which it does. It worked exactly like this since introduction.

This implies, one can use it for perspectival or 3D-effects, which one can not, as the function in it's current state is only good for displaying uniformed colored rectangles transformed, any pictorially content will be displayed falsely distorted.
"This screwdriver is no good at hitting nails, how dare you"

upload_2017-9-11_16-50-12.png
live demo
Code:
texture_set_interpolation(false);
draw_sprite_ext(top, 0, 0, 0, 2, 2, 0, -1, 1);
draw_sprite_ext(side, 0, 0, 32, 2, 2, 0, -1, 1);
var ix = 100;
var iy = 100;
var iz = 25;
draw_sprite_pos(top, 0,
    ix, iy - iz * 2,
    ix + iz * 2, iy - iz,
    ix, iy,
    ix - iz * 2, iy - iz,
    1);
draw_sprite_pos(side, 0,
    ix - iz * 2, iy - iz,
    ix, iy,
    ix, iy + iz,
    ix - iz * 2, iy,
    1);
draw_sprite_pos(side, 0,
    ix, iy,
    ix + iz * 2, iy - iz,
    ix + iz * 2, iy,
    ix, iy + iz,
    1);
The topic is about 2D-drawing-functions, so the hint to 3D projection is pointless.
You cannot accurately replicate perspective projection with affine transformations without substantially increasing the number of polygons or resorting to shaders for texture coordinate warping.
Already the next more complicated triangulation than the one used by YoYo fulfills the task of a correct distorted display perfectly, where the u,v-values in the center must be simply .5, .5:
Try a texture that isn't a 2x2 checkerboard pattern for a good sight at how naive your "fix" is.

Edit: This comes off as a bit too rude, but, to put it in more milder terms: if a problem remains unsolved for a while and looks like it has a simple solution, the solution is probably not all that simple or has obvious downsides.

Edit2: bonus demo
 
Last edited:

Dmi7ry

Member
OK, at first I wanted to let the thing rest, but the further replies of my thread requires another statement.
but it will put economic damage to Dmi7ri's sellings of his "Draw Sprite Pos Fixed"-offer.
It is not my asset. Anyway, no need to worry about this - be sure, it won't change sells of the asset.
 

Michael

Member
The purpose of the function is to allow fast affine transformations
Draw_sprite_pos() in the manual is described with the words:
"You can draw a sprite distorted over the area defined by the four corner coordinates."
The illustrating image belonging to this text shows a projective transformation, not an affine one.
When this function was introduced, in GMS 1.4.1451 (from 10/2014) and GMS 1.4.1490 (from 12/2014) the manual does NOT content a warning about the restricted usability of this funtion.

This warning first came in, as far as I can see, with GMS 1.4.1657 in 11/2015.

This is overwhelming evidence, that YoYo originally planned to provide the function for universal transforming purposes, and only after either users stated the distorted transformation or after they noticed it themselves, they decided not to fix the issue but to let it as it is and tagged a warning about the restricted usability upon it.

You cannot accurately replicate perspective projection with affine transformations
This is not the point, as computer mathematics is never pure and strict (except Mathematica and comparable scientific programms), but uses always numerical approximations and algorithms. A 2D Mesh can easily display a projective transformation with a convergence, so the user does not notice errors on the screen, and this is what a developer exspects from Draw_sprite_pos() according to this functions indicated purpose.

You cannot ...without substantially increasing the number of polygons
I don't know, what "substantially " means for you. I found out, that 200 triangles are more than enough and already oversized, to display any projective transformation properly. 200 triangles are substantially more than 2 triangles the draw_pos_sprite() command uses, but the time needed to draw the 200 triangles is according to my tests lesser than 1 [ms], which is certainly not substantial.

So what YoYo might have done is just to introduce a possibility to control the triangulation, for example through the count of horizontal and vertical subdivisions of the image-quad:
Draw_sprite_posXT(sprite, subimg, x1, y1, x2, y2, x3, y3, x4, y4, alpha, subdiv_horiz, subdiv_vert);

Indeed my first approach to solve the problem as told in post #8 of this thread fails, what I missed, because I used a chequered sprite which worked with the triangulation set. This failure does not show naivity, but is just one step of the usual iteration process of trying/testing/retrying, when someone is seriously willing to solve a problem instead of letting the things as they are and adding a message "good only for restricted use".

At last I came to this result (the code may be used in any way one finds useful):
Original sprite:
bunny_04.png
Display in GMS:
draw_sprite_posXT_03.png
With the mesh shown:
draw_sprite_posXT_04.png
Original sprite:
test_texture3.png
Display in GMS:
draw_sprite_posXT_01.png
And this is the underlying code (the code may be used in any way one finds useful):
In an objects CREATE()-Event:
Code:
//parameter of interface:
update =true;//true: calculates one time vertices for given parameters

cursprite =sprite0;
cursubimage =0;
curcolor =c_white;
curalpha =1;
curblendmode =bm_normal;
curscaleX =1;
curscaleY =1;

topleftX =50;
topleftY =100;
toprightX =350;
toprightY =50;
bottomrightX =250;
bottomrightY =500;
bottomleftX =75;
bottomleftY =300;

divhoriz =10;
divvert =10;

drawcontour =false;
drawcontourcolor =c_red;

drawmesh =false;
drawmeshcolor =c_white;
drawmeshblendmode =bm_normal;

draw_ms =false;
draw_ms_color =c_blue;

//internally used:
curspritetex =-1;
tri_x =ds_list_create();
tri_y =ds_list_create();
tri_u =ds_list_create();
tri_v =ds_list_create();
tri_col =ds_list_create();
tri_alpha =ds_list_create();
In an objects DRAW()-Event:
Code:
if !sprite_exists(cursprite)
{
    exit;
}
draw_set_blend_mode(curblendmode);
///////////////////////////////////////////////////////////////////////
//DRAW_SPRITE_POS():
var offsetx =500;//just to shift this display to the left
draw_set_color(c_blue);
draw_text(10 +offsetx, 10, "Draw_Sprite_pos():");
draw_sprite_pos(cursprite, cursubimage,
                topleftX +offsetx, topleftY,
                toprightX +offsetx, toprightY,
                bottomrightX +offsetx, bottomrightY,
                bottomleftX +offsetx, bottomleftY, curalpha);
///////////////////////////////////////////////////////////////////////
draw_set_color(c_blue);
draw_text(10, 10, "Draw_Sprite_posXT():");
//DRAW_SPRITE_POSXT():
if update
{
    ds_list_clear(tri_x);
    ds_list_clear(tri_y);
    ds_list_clear(tri_u);
    ds_list_clear(tri_v);
    ds_list_clear(tri_col);
    ds_list_clear(tri_alpha); 
  
    curspritetex =sprite_get_texture(cursprite, cursubimage); 
      
    //at first get all corners of the dividing sub-quads:
    divhoriz =max(1, divhoriz);
    divvert =max(1, divvert);
  
    var deltaxtop =(toprightX -topleftX) *curscaleX /divhoriz;
    var deltaxbottom =(bottomrightX -bottomleftX) *curscaleX /divhoriz;
    var deltayleft =(bottomleftY -topleftY)  *curscaleY /divvert;
    var deltayright =(bottomrightY -toprightY) *curscaleY /divvert;
    var n, m;
    for (m =0; m <=divhoriz; m +=1)
    {
        var xtmp =topleftX +m *deltaxtop;
        var xbottomcur =bottomleftX +m *deltaxbottom;
  
        for (n =0; n <=divvert; n +=1)
        { 
            var xcur =xtmp +((xbottomcur -xtmp) *n)/divvert;
          
            var ycur =topleftY +n *deltayleft;
            var yrightcur =toprightY +n *deltayright;
            ycur +=((yrightcur -ycur) *m) /divhoriz;
          
            pt_x[m, n] =xcur;
            pt_y[m, n] =ycur;
          
            pt_u[m, n] =m /divhoriz;
            pt_v[m, n] =n /divvert;         
          
            pt_col[m, n] =curcolor;
            pt_alpha[m, n] =curalpha;
        }
    } 
  
    //next make 2 triangles for each sub-squad:
    for (m =0; m <divhoriz; m +=1)
    {
        var mm =m +1;
        for (n =0; n <divvert; n +=1)
        {
            var nn =n +1;
            //triangle #1:     
            ds_list_add(tri_x, pt_x[m, n]);
            ds_list_add(tri_y, pt_y[m, n]);
            ds_list_add(tri_u, pt_u[m, n]);
            ds_list_add(tri_v, pt_v[m, n]);           
            ds_list_add(tri_col, pt_col[m, n]);
            ds_list_add(tri_alpha, pt_alpha[m, n]);         
          
            ds_list_add(tri_x, pt_x[mm, n]);
            ds_list_add(tri_y, pt_y[mm, n]);
            ds_list_add(tri_u, pt_u[mm, n]);
            ds_list_add(tri_v, pt_v[mm, n]);         
            ds_list_add(tri_col, pt_col[mm, n]);
            ds_list_add(tri_alpha, pt_alpha[mm, n]);         
          
            ds_list_add(tri_x, pt_x[mm, nn]);
            ds_list_add(tri_y, pt_y[mm, nn]);
            ds_list_add(tri_u, pt_u[mm, nn]);
            ds_list_add(tri_v, pt_v[mm, nn]);         
            ds_list_add(tri_col, pt_col[mm, nn]);
            ds_list_add(tri_alpha, pt_alpha[mm, nn]);         
              
            //triangle #2:
            ds_list_add(tri_x, pt_x[m, n]);
            ds_list_add(tri_y, pt_y[m, n]);
            ds_list_add(tri_u, pt_u[m, n]);
            ds_list_add(tri_v, pt_v[m, n]);         
            ds_list_add(tri_col, pt_col[m, n]);
            ds_list_add(tri_alpha, pt_alpha[m, n]);                     
          
            ds_list_add(tri_x, pt_x[mm, nn]);
            ds_list_add(tri_y, pt_y[mm, nn]);
            ds_list_add(tri_u, pt_u[mm, nn]);
            ds_list_add(tri_v, pt_v[mm, nn]);         
            ds_list_add(tri_col, pt_col[mm, nn]);
            ds_list_add(tri_alpha, pt_alpha[mm, nn]);                     
          
            ds_list_add(tri_x, pt_x[m, nn]);
            ds_list_add(tri_y, pt_y[m, nn]);   
            ds_list_add(tri_u, pt_u[m, nn]);
            ds_list_add(tri_v, pt_v[m, nn]);                 
            ds_list_add(tri_col, pt_col[m, nn]);
            ds_list_add(tri_alpha, pt_alpha[m, nn]);                     
        }
    }
    update =false;
}

var ms_start;
if draw_ms
{
    ms_start =current_time;
}

draw_primitive_begin_texture(pr_trianglelist, curspritetex);
var curcount =0;
var i;
for (i =0; i <ds_list_size(tri_x); i +=1)
{
    draw_vertex_texture_color(ds_list_find_value(tri_x, i),
                              ds_list_find_value(tri_y, i),
                              ds_list_find_value(tri_u, i),                                                         
                              ds_list_find_value(tri_v, i),
                              ds_list_find_value(tri_col, i),
                              ds_list_find_value(tri_alpha, i));                                                                                                                                                                           
    curcount +=1;                           
    if (curcount ==999)
    {
        //draw_primitive_... does not like to draw more
        //than 1000 vertices at a time
        draw_primitive_end();
        draw_primitive_begin_texture(pr_trianglelist, curspritetex);
        curcount =0;
    }
}
draw_primitive_end();

if draw_ms
{
    draw_set_blend_mode(bm_normal);
    draw_set_color(draw_ms_color);
    draw_text(10, 30, "[ms] needed :" +string(current_time -ms_start));
}


if drawmesh
{
    draw_set_color(drawmeshcolor);
    draw_set_blend_mode(drawmeshblendmode);
    var i;
    for (i =0; i <ds_list_size(tri_x); i +=3)
    {
        draw_circle(ds_list_find_value(tri_x, i),
                    ds_list_find_value(tri_y, i),
                    4, false);
        draw_circle(ds_list_find_value(tri_x, i +1),
                    ds_list_find_value(tri_y, i +1),
                    4, false);
        draw_circle(ds_list_find_value(tri_x, i +2),
                    ds_list_find_value(tri_y, i +2),
                    4, false);
                  
        draw_line(ds_list_find_value(tri_x, i),
                  ds_list_find_value(tri_y, i),
                  ds_list_find_value(tri_x, i +1),
                  ds_list_find_value(tri_y, i +1));
        draw_line(ds_list_find_value(tri_x, i +1),
                  ds_list_find_value(tri_y, i +1),
                  ds_list_find_value(tri_x, i +2),
                  ds_list_find_value(tri_y, i +2));                                                                                                                                                         
        draw_line(ds_list_find_value(tri_x, i +2),
                  ds_list_find_value(tri_y, i +2),
                  ds_list_find_value(tri_x, i),
                  ds_list_find_value(tri_y, i));               
          
    }
}

if drawcontour
{
    draw_set_color(drawcontourcolor);
    draw_line(topleftX, topleftY, toprightX, toprightY);
    draw_line(toprightX, toprightY, bottomrightX, bottomrightY);
    draw_line(bottomrightX, bottomrightY, bottomleftX, bottomleftY); 
    draw_line(bottomleftX, bottomleftY, topleftX, topleftY); 
}
///////////////////////////////////////////////////////////////////////
In an objects DESTROY-Event:
Code:
if (tri_x >-1)
{
    ds_list_destroy(tri_x);
    ds_list_destroy(tri_y);
    ds_list_destroy(tri_u);
    ds_list_destroy(tri_v);
    ds_list_destroy(tri_col);
    ds_list_destroy(tri_alpha);
}
 
Last edited:

Mike

nobody important
GMC Elder
This is not a bug.

This function draws a sprite without perspective correction as 2 triangles- it always has. It's a simple way to sheer and manipulate a sprite, but it's very much done with 2 triangles, in a non-perspective environment. This produces exactly the same results as the like of the PS1 does - as it also didn't do perspective correction.

This will not change.

If you want perspective correction, you need a 3D matrix and render using primitives - or you can break it apart yourself and do a higher resolution approximation by using a mesh. But it should be noted, that this is still wrong, and each "quad" within them mesh will still sheer incorrectly if too large. Perspective correction is the only way to get it correct.
 
Status
Not open for further replies.
Top