Pages: 1
  Print  
Author Topic: GML: draw_sprite_tiled + draw_sprite_tiled_ext  (Read 12093 times)
Offline (Unknown gender) TheExDeus
Posted on: September 25, 2010, 11:05:31 am

Developer
Joined: Apr 2008
Posts: 1860

View Profile
Notice: These functions work with any sprite size, but the functionality in _ext differs from GM one. The difference is with scaling. x and y arguments, according to manual, is "(x,y) is the place where one of the sprites is drawn". In GM these arguments are modified with scaling, so, for example, when you have a 100x100 sprite, you tile it with 0.5 scale, and put 25 in x and y position, then you won't have a sprite drawn in 25,25 as manual suggests, but you will have one drawn at 12.5,12.5. In this code this it will be drawn in 25,25. This can be changed with two parenthesis, but I think this is how the function needed to work in the first place.

Function: draw_sprite_tiled(int spr,int subimg,double x,double y);

GSsprite.h:
Code: [Select]
int draw_sprite_tiled(int spr,int subimg,double x,double y);
GSsprite.cpp:
Code: [Select]
int draw_sprite_tiled(int spr,int subimg,double x,double y)
{
  enigma::sprite *spr2d = enigma::spritestructarray[spr];
  if (!spr2d)
    return -1;

  if (enigma::cur_bou_tha_noo_sho_eve_cha_eve != spr2d->texturearray[subimg % spr2d->subcount])
  {
    glBindTexture(GL_TEXTURE_2D,spr2d->texturearray[subimg % spr2d->subcount]);
    enigma::cur_bou_tha_noo_sho_eve_cha_eve = spr2d->texturearray[subimg % spr2d->subcount];
  }


  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);


  glPushAttrib(GL_CURRENT_BIT);
    glColor4f(1,1,1,1);

    const float tbx=spr2d->texbordx,tby=spr2d->texbordy,
        xoff=spr2d->xoffset+x,
        yoff=spr2d->yoffset+y;
    const int hortil= int (ceil(room_width/(spr2d->width*tbx))),
        vertil= int (ceil(room_height/(spr2d->height*tby)));
    glBegin(GL_QUADS);
    for (int i=0; i<hortil; i++){
        for (int c=0; c<vertil; c++){
      glTexCoord2f(0,0);
        glVertex2f(i*spr2d->width-xoff,c*spr2d->height-yoff);
      glTexCoord2f(tbx,0);
        glVertex2f((i+1)*spr2d->width-xoff,c*spr2d->height-yoff);
      glTexCoord2f(tbx,tby);
        glVertex2f((i+1)*spr2d->width-xoff,(c+1)*spr2d->height-yoff);
      glTexCoord2f(0,tby);
        glVertex2f(i*spr2d->width-xoff,(c+1)*spr2d->height-yoff);
        }
    }
    glEnd();

    glPopAttrib();
    return 0;
}

Function:draw_sprite_tiled_ext(int spr,int subimg,double x,double y,double xscale,double yscale,int color,double alpha);

GSsprite.h:
Code: [Select]
int draw_sprite_tiled_ext(int spr,int subimg,double x,double y,double xscale,double yscale,int color,double alpha);
GSsprite.cpp:
Code: [Select]
int draw_sprite_tiled_ext(int spr,int subimg,double x,double y, double xscale,double yscale,int color,double alpha)
{
  enigma::sprite *spr2d = enigma::spritestructarray[spr];
  if (!spr2d)
    return -1;

  if (enigma::cur_bou_tha_noo_sho_eve_cha_eve != spr2d->texturearray[subimg % spr2d->subcount])
  {
    glBindTexture(GL_TEXTURE_2D,spr2d->texturearray[subimg % spr2d->subcount]);
    enigma::cur_bou_tha_noo_sho_eve_cha_eve = spr2d->texturearray[subimg % spr2d->subcount];
  }

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);


  glPushAttrib(GL_CURRENT_BIT);
  glColor4ub(__GETR(color),__GETG(color),__GETB(color),char(alpha*255));

    const float tbx=spr2d->texbordx,tby=spr2d->texbordy,
        xoff=spr2d->xoffset*xscale+x,
        yoff=spr2d->yoffset*yscale+y;
    const int hortil= int (ceil(room_width/(spr2d->width*tbx*xscale))),
        vertil= int (ceil(room_height/(spr2d->height*tby*yscale)));
    glBegin(GL_QUADS);
    for (int i=0; i<hortil; i++){
        for (int c=0; c<vertil; c++){
      glTexCoord2f(0,0);
        glVertex2f(i*spr2d->width*xscale-xoff,c*spr2d->height*yscale-yoff);
      glTexCoord2f(tbx,0);
        glVertex2f((i+1)*spr2d->width*xscale-xoff,c*spr2d->height*yscale-yoff);
      glTexCoord2f(tbx,tby);
        glVertex2f((i+1)*spr2d->width*xscale-xoff,(c+1)*spr2d->height*yscale-yoff);
      glTexCoord2f(0,tby);
        glVertex2f(i*spr2d->width*xscale-xoff,(c+1)*spr2d->height*yscale-yoff);
        }
    }
    glEnd();

    glPopAttrib();
    return 0;
}

In attachments you can see two images.
First one is: draw_sprite_tiled(spr_0,0,50,50);
Other one is: draw_sprite_tiled_ext(spr_0,0,25,25,0.5,0.5,c_yellow,0.5);
« Last Edit: September 25, 2010, 12:40:35 pm by HaRRiKiRi » Logged
Offline (Male) Josh @ Dreamland
Reply #1 Posted on: October 16, 2010, 01:55:48 pm

Prince of all Goldfish
Developer
Location: Pittsburgh, PA, USA
Joined: Feb 2008
Posts: 2950

View Profile Email
Your choice to use room_width and height disregards views. Other than that, I guess I don't find fault with this.
I should also mention that I appreciate your single use of GL_QUADS, as opposed to one each for() iteration.
« Last Edit: October 16, 2010, 01:58:25 pm by Josh @ Dreamland » Logged
"That is the single most cryptic piece of code I have ever seen." -Master PobbleWobble
"I disapprove of what you say, but I will defend to the death your right to say it." -Evelyn Beatrice Hall, Friends of Voltaire
Offline (Unknown gender) TheExDeus
Reply #2 Posted on: October 16, 2010, 05:04:42 pm

Developer
Joined: Apr 2008
Posts: 1860

View Profile
Quote
Your choice to use room_width and height disregards views.
Yes, I did thought of using views. With one view it would be easy and it would increase the functions speed, but I figured that it would be quite hard with several views. Then you would need to title in every view, which could get slower. And if views overlapped then it would create visual artifacts (especially if the tiled sprite has alpha). So I think this is the best way. Just tile in all of the room. This functions isn't used in 10kx10k rooms anyway. At least it shouldn't. For one view and large room user can use draw_sprite_tiled_area.
Quote
I should also mention that I appreciate your single use of GL_QUADS, as opposed to one each for() iteration.
Well I am not that bad at optimizing. :D
Logged
Offline (Male) Josh @ Dreamland
Reply #3 Posted on: October 16, 2010, 09:55:24 pm

Prince of all Goldfish
Developer
Location: Pittsburgh, PA, USA
Joined: Feb 2008
Posts: 2950

View Profile Email
Oh, don't worry about that. I replaced your use of room_width and height with a simple ternary expression. Views are inefficient as dog in GM anyway.

This is the code I ended up including, which I will assume to work until further notice:

Code: [Select]
int draw_sprite_tiled(int spr,int subimg,double x,double y)
{
  enigma::sprite *spr2d = enigma::spritestructarray[spr];
  if (!spr2d)
    return -1;
 
  if (enigma::cur_bou_tha_noo_sho_eve_cha_eve != spr2d->texturearray[subimg % spr2d->subcount])
  {
    glBindTexture(GL_TEXTURE_2D,spr2d->texturearray[subimg % spr2d->subcount]);
    enigma::cur_bou_tha_noo_sho_eve_cha_eve = spr2d->texturearray[subimg % spr2d->subcount];
  }
 
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
 
  glPushAttrib(GL_CURRENT_BIT);
    glColor4f(1,1,1,1);
   
    const float
      tbx  = spr2d->texbordx,  tby  = spr2d->texbordy,
      xoff = spr2d->xoffset+x, yoff = spr2d->yoffset+y;
    const int
      hortil = int (ceil(   (view_enabled ? view_xview[view_current] + view_wview[view_current] : room_width)    / (spr2d->width*tbx))),
      vertil = int (ceil(   (view_enabled ? view_yview[view_current] + view_hview[view_current] : room_height)   / (spr2d->height*tby)));
   
    glBegin(GL_QUADS);
    for (int i=0; i<hortil; i++)
    {
      for (int c=0; c<vertil; c++)
      {
        glTexCoord2f(0,0);
          glVertex2f(i*spr2d->width-xoff,c*spr2d->height-yoff);
        glTexCoord2f(tbx,0);
          glVertex2f((i+1)*spr2d->width-xoff,c*spr2d->height-yoff);
        glTexCoord2f(tbx,tby);
          glVertex2f((i+1)*spr2d->width-xoff,(c+1)*spr2d->height-yoff);
        glTexCoord2f(0,tby);
          glVertex2f(i*spr2d->width-xoff,(c+1)*spr2d->height-yoff);
      }
    }
    glEnd();
  glPopAttrib();
  return 0;
}

int draw_sprite_tiled_ext(int spr,int subimg,double x,double y, double xscale,double yscale,int color,double alpha)
{
  enigma::sprite *spr2d = enigma::spritestructarray[spr];
  if (!spr2d)
    return -1;

  if (enigma::cur_bou_tha_noo_sho_eve_cha_eve != spr2d->texturearray[subimg % spr2d->subcount])
  {
    glBindTexture(GL_TEXTURE_2D,spr2d->texturearray[subimg % spr2d->subcount]);
    enigma::cur_bou_tha_noo_sho_eve_cha_eve = spr2d->texturearray[subimg % spr2d->subcount];
  }

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);


  glPushAttrib(GL_CURRENT_BIT);
  glColor4ub(__GETR(color),__GETG(color),__GETB(color),char(alpha*255));

    const float
      tbx  = spr2d->texbordx,         tby  = spr2d->texbordy,
      xoff = spr2d->xoffset*xscale+x, yoff = spr2d->yoffset*yscale+y;
    const int
      hortil= int (ceil(   (view_enabled ? view_xview[view_current] + view_wview[view_current] : room_width)    / (spr2d->width*tbx*xscale))),
      vertil= int (ceil(   (view_enabled ? view_yview[view_current] + view_hview[view_current] : room_height)   / (spr2d->height*tby*yscale)));
   
    glBegin(GL_QUADS);
    for (int i=0; i<hortil; i++)
    {
      for (int c=0; c<vertil; c++)
      {
        glTexCoord2f(0,0);
          glVertex2f(i*spr2d->width*xscale-xoff,c*spr2d->height*yscale-yoff);
        glTexCoord2f(tbx,0);
          glVertex2f((i+1)*spr2d->width*xscale-xoff,c*spr2d->height*yscale-yoff);
        glTexCoord2f(tbx,tby);
          glVertex2f((i+1)*spr2d->width*xscale-xoff,(c+1)*spr2d->height*yscale-yoff);
        glTexCoord2f(0,tby);
          glVertex2f(i*spr2d->width*xscale-xoff,(c+1)*spr2d->height*yscale-yoff);
      }
    }
    glEnd();

    glPopAttrib();
    return 0;
}

I have one bone left to pick with it, being that it does not account for a special-case optimization in which the bind mode can be set to GL_WRAP and the sprite can just be drawn large. This can be checked for simply by testing that both spr->texbordx and texbordy are 1. In this case, the entirety of the allocated space is used, and there is no need to loop anything (The GPU is, of course, much more proficient at menial looping than the CPU).

My recommendation, as I will soon implement if no one else does, is a pattern like so:

if (spr->texbordx == 1)
{
  if (spr->texbordy == 1)
  {
     Draw single quad with appropriate texture bounds. Negative and extremely large bounds are both valid.
  }
  else
    for (all Y coordinates)
     draw a large horizontal quad with appropriate x bounds, but with texbordy as the y bound.
}
else
  if (spr->texbordy == 1)
    for (all X coordinates)
     draw a large horizontal quad with appropriate Y bounds, but with texbordy as the Y bound.
  else
   Exactly what you do now.

Granted, that's a "fucking large function," and with two branches, no less. The average time saving becomes a question, and we have to wonder if it was worth it for the extra checks and the extra memory. (Both of which are relatively negligible; it's all a matter of weight).



And this is the shit I mull over all day and night.
Logged
"That is the single most cryptic piece of code I have ever seen." -Master PobbleWobble
"I disapprove of what you say, but I will defend to the death your right to say it." -Evelyn Beatrice Hall, Friends of Voltaire
Offline (Unknown gender) TheExDeus
Reply #4 Posted on: October 17, 2010, 04:51:30 am

Developer
Joined: Apr 2008
Posts: 1860

View Profile
Nevermind..
« Last Edit: October 17, 2010, 04:56:10 am by HaRRiKiRi » Logged
Offline (Male) Josh @ Dreamland
Reply #5 Posted on: October 17, 2010, 08:15:30 am

Prince of all Goldfish
Developer
Location: Pittsburgh, PA, USA
Joined: Feb 2008
Posts: 2950

View Profile Email
What am I never minding?
Logged
"That is the single most cryptic piece of code I have ever seen." -Master PobbleWobble
"I disapprove of what you say, but I will defend to the death your right to say it." -Evelyn Beatrice Hall, Friends of Voltaire
Pages: 1
  Print