How to avoid 'smearing'/trails of sprites during movement

Hey gang,

Any ideas how to avoid this?

I tried adding padding to the sprite that contains the red triangle; it helped a bit on the top and sides, but the bottom edge was always bad. The video here is with zero padding.

I'll post code next.

Any help would be greatly appreciated.

Happy new year!
Dax.

Looks like you are not erasing the field completely before drawing the next image.

EDIT: Full code posted below.

Hey @jremington , thanks for your reply.

The snippet doesn't help your cause. Always post all the code.

Okay, thanks. I'll create a cut-down version now as there are currently 700+ lines of code and not many are for graphics.

The easiest is to put a white edge around the sprite that is bigger that the step the pointer can make. Or before you print the arrow in it's new spot, print the arrow in it's old spot in white. This may cause some flashing though, so only do that when the spot has changed.

I agree that there's too much code to read, so a general tip. Yes, I've written sprites. I don't know how you are doing it. In my case, I always knew the size and location of the last sprite to be written, so it was easy to erase it. I believe you can't really go wrong if you follow that.

#include <Arduino.h>
#include <esp_adc_cal.h>
#include <SPI.h>
#include <FS.h> // Font files are stored in SPIFFS, so load the library

const int joyX = 39; //pin number
const int joyY = 36;
const int joyR = 35;
const int buttonA = 16;
const int buttonB = 21;
const int touchSensor = 32; //pin number
const int touchThresh = 15;

const int stepperSpeed[] = {4, 16, 50, 100}; // 4 different speeds, used for acceleration
const int servoSpeed[] = {10, 35, 70, 140};
const int joyXYThresh[] = {0, 50, 530, 850, 1500, 00000, 2200, 2700, 3100, 3800}; //12-bit ADC: there is a +/-300 null space in the middle (1885) of the joystick range
const int joyRThresh[] = {0, 50, 530, 850, 1500, 00000, 2200, 2700, 3100, 3800};  //12-bit ADC: there is a +/-300 null space in the middle (1885) of the joystick range
int joyXVal;
int joyYVal;
int joyRVal;

int L_XposReq = 0;
int L_YposReq = 0;
int L_RposReq = 1500;
bool L_touch;
int prevXval;
int prevYval;
int prevRval;

int stepperSpeedMode;
int servoSpeedMode;
int XposMin = 0; //! These 6 need to be read from calibration/last-position data (SPIFFS)
int YposMin = 0;
int RposMin = 500;
int XposMax = 10000;
int YposMax = 5000;
int RposMax = 2500;

// Graphics-related declarations
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI(); // Use hardware SPI
#define AA_FONT_SMALL "NotoSansBold15"
#define AA_FONT_LARGE "NotoSansBold36"
#define BGCOLOUR TFT_WHITE
TFT_eSprite micPointArrow = TFT_eSprite(&tft); // Create Sprite object "micPointArrow" with pointer to "tft" object. The pointer is used with *.pushSprite() to push it onto the TFT
const int micPointArrow_colour = TFT_RED;
const int micPointArrow_padding = 2;
const int micPointArrow_w = 18 + 2 * micPointArrow_padding;
const int micPointArrow_h = 28 + 2 * micPointArrow_padding;

const int resolution_x = 480; //^ Set screen resolution
const int resolution_y = 320;
const int physicalTravelX_mm = 450; //^ Physical travel of axis in millimetres
const int physicalTravelY_mm = 200;
const int positionGraph_w = 340;
const int positionGraph_h = (float)physicalTravelY_mm / physicalTravelX_mm * positionGraph_w;
const int positionGraph_x = (resolution_x - positionGraph_w - micPointArrow_w - 3);
const int positionGraph_y = 55;
const int graphBoxThickness = 2;


//^    ****************  SETUP STARTS HERE  ****************

void setup()
{
  /*=========================================================================
    Physical I/O
    =========================================================================*/
  pinMode(joyX, INPUT);
  pinMode(joyY, INPUT);
  pinMode(joyR, INPUT);
  pinMode(buttonA, INPUT); //using resistor for pull-up.
  pinMode(buttonB, INPUT); //using resistor for pull-up.
  //pinMode(touchSensor, INPUT); //? not necessary?

  /*=========================================================================
    SPIFFS setup
    =========================================================================*/

  if (!SPIFFS.begin())
  {
    Serial.println("SPIFFS initialisation failed!");
    while (1)
      yield(); // Stay here twiddling thumbs waiting
  }
  Serial.println("\r\nSPIFFS available!");

  // ESP32 will crash if any of the fonts are missing
  bool font_missing = false;
  if (SPIFFS.exists("/NotoSansBold15.vlw") == false)
    font_missing = true;
  if (SPIFFS.exists("/NotoSansBold36.vlw") == false)
    font_missing = true;

  if (font_missing)
  {
    Serial.println("\r\nFont missing in SPIFFS, did you upload it? (See Font_Notes.ino for details)");
    while (1)
      yield();
  }
  else
    Serial.println("\r\nFonts found OK.");

  /*=========================================================================
    TFT graphics display setup
    =========================================================================*/

  tft.begin(); //? Maybe tft.init() ?? Begin seems to work
  tft.setRotation(3);
  tft.fillScreen(BGCOLOUR);
  tft.setTextColor(TFT_BLACK, BGCOLOUR); // Set the font colour AND the background colour so the anti-aliasing works
  tft.loadFont(AA_FONT_SMALL);           // Must load the font first
  tft.setCursor(0, 0);                   // Set cursor at top left of screen

  Serial.begin(115200);                        // debug only
  Serial.println("serial monitoring enabled"); // debug only
  Serial.println("Graphics library started");
}

//^  ******************************************************************
//^    ****************  MAIN FUNCTION STARTS HERE  ****************
//^  ******************************************************************

void loop()
{
  stepperXControl(); //run this routine
  stepperYControl();
  servoControl();
  updateDisplayDynamic();
}

//^    ****************  STEPPER X Control STARTS HERE  ****************
//^    *****************************************************************

void stepperXControl()
{
  joyXVal = analogRead(joyX);
  {
    if (L_XposReq > XposMin) //This block applies acceleration based on joystick position
    {
      if (joyXVal <= joyXYThresh[1]) //decreasing fast
      {
        L_XposReq -= stepperSpeed[3];
        stepperSpeedMode = -4;
      }

      if (joyXVal > joyXYThresh[1] && joyXVal <= joyXYThresh[2]) //decreasing medium
      {
        L_XposReq -= stepperSpeed[2];
        stepperSpeedMode = -3;
      }

      if (joyXVal > joyXYThresh[2] && joyXVal <= joyXYThresh[3]) //decreasing fine
      {
        L_XposReq -= stepperSpeed[1];
        stepperSpeedMode = -2;
      }

      if (joyXVal > joyXYThresh[3] && joyXVal <= joyXYThresh[4]) //decreasing super-fine
      {
        L_XposReq -= stepperSpeed[0];
        stepperSpeedMode = -1;
      }
    }

    if (joyXVal > joyXYThresh[4] && joyXVal < joyXYThresh[6]) //centre position
    {
      stepperSpeedMode = 0;
    }

    if (L_XposReq < XposMax)
    {
      if (joyXVal >= joyXYThresh[6] && joyXVal < joyXYThresh[7]) //increasing super-fine
      {
        L_XposReq += stepperSpeed[0];
        stepperSpeedMode = 1;
      }

      if (joyXVal >= joyXYThresh[7] && joyXVal < joyXYThresh[8]) //increasing fine
      {
        L_XposReq += stepperSpeed[1];
        stepperSpeedMode = 2;
      }

      if (joyXVal >= joyXYThresh[8] && joyXVal < joyXYThresh[9]) //increasing medium
      {
        L_XposReq += stepperSpeed[2];
        stepperSpeedMode = 3;
      }

      if (joyXVal >= joyXYThresh[9]) //increasing fast
      {
        L_XposReq += stepperSpeed[3];
        stepperSpeedMode = 4;
      }
    }

    if (abs(stepperSpeedMode) == 1) //effectively adds extra precision
    {
      delay(100);
    }
    else if (abs(stepperSpeedMode) == 2)
    {
      delay(30);
    }
    else
    {
      delay(10);
    }
  }

  // Enforce min/max limits
  if (L_XposReq < XposMin)
  {
    L_XposReq = XposMin;
  }
  if (L_XposReq > XposMax)
  {
    L_XposReq = XposMax;
  }

  //  stepperX.write(L_XposReq); //¡ The business.
}

//^    ****************  STEPPER Y Control STARTS HERE  ****************
//^    *****************************************************************

void stepperYControl()
{
  joyYVal = analogRead(joyY);
  {
    if (joyYVal <= joyXYThresh[1]) //decreasing fast
    {
      L_YposReq -= stepperSpeed[3];
      stepperSpeedMode = -4;
    }

    if (joyYVal > joyXYThresh[1] && joyYVal <= joyXYThresh[2]) //decreasing medium
    {
      L_YposReq -= stepperSpeed[2];
      stepperSpeedMode = -3;
    }

    if (joyYVal > joyXYThresh[2] && joyYVal <= joyXYThresh[3]) //decreasing fine
    {
      L_YposReq -= stepperSpeed[1];
      stepperSpeedMode = -2;
    }

    if (joyYVal > joyXYThresh[3] && joyYVal <= joyXYThresh[4]) //decreasing super-fine
    {
      L_YposReq -= stepperSpeed[0];
      stepperSpeedMode = -1;
    }

    if (joyYVal > joyXYThresh[4] && joyYVal < joyXYThresh[6]) //centre position
    {
      stepperSpeedMode = 0;
    }

    if (joyYVal >= joyXYThresh[6] && joyYVal < joyXYThresh[7]) //increasing super-fine
    {
      L_YposReq += stepperSpeed[0];
      stepperSpeedMode = 1;
    }

    if (joyYVal >= joyXYThresh[7] && joyYVal < joyXYThresh[8]) //increasing fine
    {
      L_YposReq += stepperSpeed[1];
      stepperSpeedMode = 2;
    }

    if (joyYVal >= joyXYThresh[8] && joyYVal < joyXYThresh[9]) //increasing medium
    {
      L_YposReq += stepperSpeed[2];
      stepperSpeedMode = 3;
    }

    if (joyYVal >= joyXYThresh[9]) //increasing fast
    {
      L_YposReq += stepperSpeed[3];
      stepperSpeedMode = 4;
    }

    if (abs(stepperSpeedMode) == 1) //effectively adds extra precision
    {
      delay(100);
    }
    else if (abs(stepperSpeedMode) == 2)
    {
      delay(30);
    }
    else
    {
      delay(10);
    }
  }

  // Enforce min/max limits
  if (L_YposReq < YposMin)
  {
    L_YposReq = YposMin;
  }
  if (L_YposReq > YposMax)
  {
    L_YposReq = YposMax;
  }

  //  stepperY.write(L_YposReq); //¡ The business.
}

//^    ****************  SERVO Control STARTS HERE  ****************
//^    *****************************************************************

void servoControl()
{
  joyRVal = analogRead(joyR);
  {
    if (joyRVal <= joyRThresh[1]) //decreasing fast
    {
      L_RposReq -= servoSpeed[3];
      servoSpeedMode = -4;
    }

    if (joyRVal > joyRThresh[1] && joyRVal <= joyRThresh[2]) //decreasing medium
    {
      L_RposReq -= servoSpeed[2];
      servoSpeedMode = -3;
    }

    if (joyRVal > joyRThresh[2] && joyRVal <= joyRThresh[3]) //decreasing fine
    {
      L_RposReq -= servoSpeed[1];
      servoSpeedMode = -2;
    }

    if (joyRVal > joyRThresh[3] && joyRVal <= joyRThresh[4]) //decreasing super-fine
    {
      L_RposReq -= servoSpeed[0];
      servoSpeedMode = -1;
    }

    if (joyRVal > joyRThresh[4] && joyRVal < joyRThresh[6]) //centre position
    {
      servoSpeedMode = 0;
    }

    if (joyRVal >= joyRThresh[6] && joyRVal < joyRThresh[7]) //increasing super-fine
    {
      L_RposReq += servoSpeed[0];
      servoSpeedMode = 1;
    }

    if (joyRVal >= joyRThresh[7] && joyRVal < joyRThresh[8]) //increasing fine
    {
      L_RposReq += servoSpeed[1];
      servoSpeedMode = 2;
    }

    if (joyRVal >= joyRThresh[8] && joyRVal < joyRThresh[9]) //increasing medium
    {
      L_RposReq += servoSpeed[2];
      servoSpeedMode = 3;
    }

    if (joyRVal >= joyRThresh[9]) //increasing fast
    {
      L_RposReq += servoSpeed[3];
      servoSpeedMode = 4;
    }

    if (abs(servoSpeedMode) == 1) //effectively adds extra precision
    {
      delay(100);
    }
    else if (abs(servoSpeedMode) == 2)
    {
      delay(30);
    }
    else
    {
      delay(10);
    }
  }

  // Enforce min/max limits
  if (L_RposReq < RposMin)
  {
    L_RposReq = RposMin;
  }
  if (L_RposReq > RposMax)
  {
    L_RposReq = RposMax;
  }
  //  servo.write(L_RposReq); //¡ The business.
}

//^    ****************  Dynamic display function STARTS HERE  ****************
//^    ***********************************************************************

void updateDisplayDynamic()
{
  //- Map variables to graph bounds
  int plotX = map(L_XposReq, XposMin, XposMax, positionGraph_x, positionGraph_x + positionGraph_w); // variable, fromLow, fromHigh, toLow, toHigh
  int plotY = map(L_YposReq, YposMin, YposMax, positionGraph_y + positionGraph_h, positionGraph_y); //^ inverted
  int plotR = map(L_RposReq, RposMin, RposMax, -45, 45);

  //- Clear mic arrow pointer sprite
  micPointArrow.fillSprite(BGCOLOUR); //Since sprite hasn't yet been created, these 2 lines should do nothing until after the mic pointer arrow is first drawn (below)
  micPointArrow.pushRotated(plotR);

  //- Draw mic arrow pointer sprite
  micPointArrow.setColorDepth(8);
  micPointArrow.createSprite(micPointArrow_w, micPointArrow_h); // Create an 8 bit sprite 20*30 pixels (uses 600 bytes of RAM)
  tft.setPivot(plotX, plotY);                                   //anchor point on LCD
  uint16_t micPointpiv_x = micPointArrow.width() / 2;           // Define sprite pivot point. x pivot of Sprite (middle).
  uint16_t micPointpiv_y = 0 + micPointArrow_padding;           // y pivot of Sprite (0 = top row of pixels)
  micPointArrow.setPivot(micPointpiv_x, micPointpiv_y);         // Set pivot point of this Sprite
  micPointArrow.fillSprite(BGCOLOUR);

  micPointArrow.fillTriangle(9 + micPointArrow_padding, 1 + micPointArrow_padding, 0 + micPointArrow_padding, 28 + micPointArrow_padding, 18 + micPointArrow_padding, 28 + micPointArrow_padding, micPointArrow_colour); //Apex X,Y , Left bottom X,Y , Right bottom X,Y
  micPointArrow.pushRotated(plotR, BGCOLOUR);

  micPointArrow.deleteSprite(); // Delete it to free-up memory

  //- Draw graph bounding box [STATIC ELEMENT, redrawn on each refresh]
  //^ Mitred line T,B,L,R method
  for (int i = 0; i <= (graphBoxThickness - 1); i++)
  {
    tft.drawLine(positionGraph_x - i, positionGraph_y - i, positionGraph_x + positionGraph_w + i, positionGraph_y - i, TFT_LIGHTGREY);                                     // top
    tft.drawLine(positionGraph_x - i, positionGraph_y + positionGraph_h + i, positionGraph_x + positionGraph_w + i, positionGraph_y + positionGraph_h + i, TFT_LIGHTGREY); //bottom
    tft.drawLine(positionGraph_x - i, positionGraph_y - i, positionGraph_x - i, positionGraph_y + positionGraph_h + i, TFT_LIGHTGREY);                                     //left
    tft.drawLine(positionGraph_x + positionGraph_w + i, positionGraph_y - i, positionGraph_x + positionGraph_w + i, positionGraph_y + positionGraph_h + i, TFT_LIGHTGREY); //right
  }
}

Hey @Deva_Rishi, thank you. This is what I thought of as a work-around, but it doesn't seem to be solving the problem as you can see in the video. Even increasing micPointArrow_padding to 4 (pixels all around) doesn't solve the problem.

You shouldn't need padding.

The padding doesn't do anything, increase the actual size of the sprite. Or just draw a filled-rectangle where the sprite was before, in the background color, before you draw the sprite.

Yes, I believe the padding does increase the sprite size:

const int micPointArrow_padding = 2;
const int micPointArrow_w = 18 + 2 * micPointArrow_padding;
const int micPointArrow_h = 28 + 2 * micPointArrow_padding;
...
micPointArrow.fillTriangle(9 + micPointArrow_padding, 1 + micPointArrow_padding, 0 + micPointArrow_padding, 28 + micPointArrow_padding, 18 + micPointArrow_padding, 28 + micPointArrow_padding, micPointArrow_colour); //Apex X,Y , Left bottom X,Y , Right bottom X,Y

The "+ micPointArrow_padding" in the fillTriangle line is to keep the triangle centered in the padded sprite.

Haven't looked at the code - and have absolutely no intention of doing so - but the first thing that comes to mind is that it is the base of the equilateral triangle that is causing the problem, all other parts are doing just fine.

I would look into that very carefully. :thinking:

@Paul_B, yes, that seems like an apt observation, but I can't see what could be wrong with it. This is a simplified version (without the padding offsets).

sprite.fillTriangle(9,1 , 0,28 , 18,28, colour); //Apex X,Y , Left bottom X,Y , Right bottom X,Y, colour

I just tried a different method, but getting the same result.

void updateDisplayDynamic()
{
  int plotXprev;
  int plotYprev;
  int plotRprev;
  
  //- Map variables to graph bounds
  int plotX = map(L_XposReq, XposMin, XposMax, positionGraph_x, positionGraph_x + positionGraph_w); // variable, fromLow, fromHigh, toLow, toHigh
  int plotY = map(L_YposReq, YposMin, YposMax, positionGraph_y + positionGraph_h, positionGraph_y); //^ inverted
  int plotR = map(L_RposReq, RposMin, RposMax, -45, 45);

  //- Create mic arrow pointer sprite
  micPointArrow.setColorDepth(8);
  micPointArrow.createSprite(micPointArrow_w, micPointArrow_h); // Create an 8 bit sprite 20*30 pixels (uses 600 bytes of RAM)
  micPointArrow.fillSprite(BGCOLOUR);

  //- Clear mic arrow pointer sprite
  uint16_t micPointpiv_x = micPointArrow.width() / 2;           // Define sprite pivot point. x pivot of Sprite (middle).
  uint16_t micPointpiv_y = 0 + micPointArrow_padding;           // y pivot of Sprite (0 = top row of pixels)
  micPointArrow.setPivot(micPointpiv_x, micPointpiv_y);         // Set pivot point of this Sprite
  tft.setPivot(plotXprev, plotYprev);                                   //anchor point on LCD
  micPointArrow.pushRotated(plotRprev);

  //- Draw mic arrow pointer sprite
  tft.setPivot(plotX, plotY);
  micPointArrow.fillTriangle(9 + micPointArrow_padding, 1 + micPointArrow_padding, 0 + micPointArrow_padding, 28 + micPointArrow_padding, 18 + micPointArrow_padding, 28 + micPointArrow_padding, micPointArrow_colour); //Apex X,Y , Left bottom X,Y , Right bottom X,Y
  micPointArrow.pushRotated(plotR, BGCOLOUR);

  micPointArrow.deleteSprite(); // Delete it to free-up memory

  plotXprev = plotX;
  plotYprev = plotY;
  plotRprev = plotR;
}