Add gravity to an animated graphic

Right, I see. OK. Let me give that a try. Makes sense now. Maybe I needed to sleep on it.Thank you Scott and Pharap.

2 Likes

If that’s the case then that’s easy enough…


#include <Arduboy2.h>

Arduboy2 arduboy;

// Start at the top of the screen
float positionX = (WIDTH / 2.0f);
float positionY = -8.0f;

// Start with no velocity
float velocityX = 0.0f;
float velocityY = 0.0f;

uint8_t iteration = 60;

void updateCube()
{
	// Increase velocity
	velocityY += 0.01f;

	// Update position
	positionX += velocityX;
	positionY += velocityY;

	// Cycle the rotation
	if(iteration > 0)
		--iteration;
	else
		iteration = 60;
	
}

void drawCube()
{
	constexpr int halfRad = (38 / 2);

	float SpinAng = ((iteration * 6) / 57.296f);
	float SpinAng2 = (SpinAng + (90.0f / 57.296f));

	float sin1 = sin(SpinAng);
	float cos1 = cos(SpinAng);
	float sin2 = sin(SpinAng2);
	float cos2 = cos(SpinAng2);

	float positionY2 = positionY - 40;

	int a = (positionX + (sin1 * halfRad));
	int b = (positionY + (-cos1 * halfRad)) / 2;
	int c = (positionX + (-sin1 * halfRad));
	int d = (positionY + (cos1 * halfRad)) / 2;

	int a1 = (positionX + (sin2 * halfRad));
	int b1 = (positionY + (-cos2 * halfRad)) / 2;
	int c1 = (positionX + (-sin2 * halfRad));
	int d1 = (positionY + (cos2 * halfRad)) / 2;

	// Draw first rectangle
	arduboy.drawLine(a, b, a1, b1, WHITE);
	arduboy.drawLine(a1, b1, c, d, WHITE);
	arduboy.drawLine(c, d, c1, d1, WHITE);
	arduboy.drawLine(c1, d1, a, b, WHITE);

	int e = (positionX + (sin1 * halfRad));
	int f = (positionY2 + (-cos1 * halfRad)) / 2;
	int g = (positionX + (-sin1 * halfRad));
	int h = (positionY2 + (cos1 * halfRad)) / 2;
	
	int e1 = (positionX + (sin2 * halfRad));
	int f1 = (positionY2 + (-cos2 * halfRad)) / 2;
	int g1 = (positionX + (-sin2 * halfRad));
	int h1 = (positionY2 + (cos2 * halfRad)) / 2;

	// Draw second rectangle
	arduboy.drawLine(e, f, e1, f1, WHITE);
	arduboy.drawLine(e1, f1, g, h, WHITE);
	arduboy.drawLine(g, h, g1, h1, WHITE);
	arduboy.drawLine(g1, h1, e, f, WHITE);

	// Join the rectangles
	arduboy.drawLine(a, b, e, f, WHITE);
	arduboy.drawLine(g, h, c, d, WHITE);
	arduboy.drawLine(a1, b1, e1, f1, WHITE);
	arduboy.drawLine(g1, h1, c1, d1, WHITE);
}

void setup()
{
	arduboy.begin();
}

void loop()
{
	if(!arduboy.nextFrame())
		return;

	arduboy.pollButtons();

	arduboy.clear();

	updateCube();
	drawCube();

	arduboy.display();
}

Accelerate.ino.hex (26.6 KB)

@Maxm, it may require some tweaking to behave more like how you want it to, but the basics are there.

Personally I’d suggest storing the cube model as an array of coordinates and then offsetting those coordinates with positionX and positionY and then doing the rotation (or doing the rotation and then offsetting, whichever’s easier).
That ought to work out cheaper than other options.

I’m still not quite sure how your spin angles are actually working,
but offering up a better solution would mean figuring out the matrices involved and I’m not really in a hurry to do that if I can avoid it.
Also I’d have to figure out which corners of the cube correspond to your letter labellings.
I.e. which corner is a, b; which is a1, b1.

(From what I gather you’re actually effectively drawing two 2D rectangles and then linking them up.)


Edit:
Using the realisation that the top and base rectangles are identical,
I found a way to cut the memory down significantly:

#include <Arduboy2.h>

Arduboy2 arduboy;

// Start at the top of the screen
float positionX = (WIDTH / 2.0f);
float positionY = -8.0f;

// Start with no velocity
float velocityX = 0.0f;
float velocityY = 0.0f;

uint8_t iteration = 60;

void updateCube()
{
	// Increase velocity
	velocityY += 0.01f;

	// Update position
	positionX += velocityX;
	positionY += velocityY;

	// Cycle the rotation
	if(iteration > 0)
		--iteration;
	else
		iteration = 60;
	
}

void drawCube()
{
	constexpr int halfRad = (38 / 2);

	float SpinAng = ((iteration * 6) / 57.296f);
	float SpinAng2 = (SpinAng + (90.0f / 57.296f));

	float sin1 = sin(SpinAng);
	float cos1 = cos(SpinAng);
	float sin2 = sin(SpinAng2);
	float cos2 = cos(SpinAng2);

	int a = (positionX + (sin1 * halfRad));
	int b = (positionY + (-cos1 * halfRad)) / 2;
	int c = (positionX + (-sin1 * halfRad));
	int d = (positionY + (cos1 * halfRad)) / 2;

	int a1 = (positionX + (sin2 * halfRad));
	int b1 = (positionY + (-cos2 * halfRad)) / 2;
	int c1 = (positionX + (-sin2 * halfRad));
	int d1 = (positionY + (cos2 * halfRad)) / 2;

	// Draw first rectangle
	arduboy.drawLine(a, b, a1, b1, WHITE);
	arduboy.drawLine(a1, b1, c, d, WHITE);
	arduboy.drawLine(c, d, c1, d1, WHITE);
	arduboy.drawLine(c1, d1, a, b, WHITE);

	int e = a;
	int f = (b + 20);
	int g = c;
	int h = (d + 20);
	
	int e1 = a1;
	int f1 = (b1 + 20);
	int g1 = c1;
	int h1 = (d1 + 20);

	// Draw second rectangle
	arduboy.drawLine(e, f, e1, f1, WHITE);
	arduboy.drawLine(e1, f1, g, h, WHITE);
	arduboy.drawLine(g, h, g1, h1, WHITE);
	arduboy.drawLine(g1, h1, e, f, WHITE);

	// Join the rectangles
	arduboy.drawLine(a, b, e, f, WHITE);
	arduboy.drawLine(g, h, c, d, WHITE);
	arduboy.drawLine(a1, b1, e1, f1, WHITE);
	arduboy.drawLine(g1, h1, c1, d1, WHITE);
}

void setup()
{
	arduboy.begin();
}

void loop()
{
	if(!arduboy.nextFrame())
		return;

	arduboy.pollButtons();

	arduboy.clear();

	updateCube();
	drawCube();

	arduboy.display();
}

(No sense recalculating data that you already have.)


@Maxm
A quick question: where did you actually get the value of 57.296 from?

I checked 90.0f / 57.296f and it is actually very close to π / 2.
(The first 5 decimal places are the same.)

1 Like

It’s interesting that without having termination logic in your example, the cube “virtually” continues to fall below the screen, within the large “off screen” space, still at constant acceleration. Eventually, the Y position wraps, so the cube is now falling from the top of “off screen” space, still accelerating. At some point the cube falls through the visible part of screen space and you see it zip by at great speed. This continues, with the cube appearing after shorter and shorter intervals but at faster and faster speeds.

I guess eventually the Y velocity would wrap (changing to maximum negative?)


Sorry for the sidetrack.
We now return you to your regularly scheduled programming.

That sounds reasonable for an integer type, but it’s a float, so potentially it might eventually reach positivie infinity, at which point addition should become equivalent to the identity operation, which means it should never wrap back around to the top and instead gets stuck at the theoretical positive infinity line…

But since the rendering operations accept integers rather than floats, that also raises the question of what value positive infinity generates when cast to an int16_t

Looking closer, you’re always only adding 0.01f to the Y velocity, so I suspect the value would only increase to the point where adding 0.01 would have no ability to increase the value, and then it would be stuck there.

By ‘it’ I meant the Y position rather than the Y velocity.

I’m not sure at what point adding 0.01f might cease to have an effect (or even if there is such a point).

One thing I can confirm though is that the cube does indeed wrap back round, but it’s due to the implicit conversion to int16_t rather than the float itself wrapping around.

I ran the program whilst printing the values of positionY and positionX, as well as the results of casting them to int16_t to observe how larger floats are interpreted.
After a point the cast value effectively wraps around and starts producing negative numbers that eventually increase into the positive range before seemingly wrapping around again.

More interestingly though, there comes a point where the program appears to halt with:

  • positionY = 4128450.75 (rounded by println)
  • static_cast<int16_t>(positionY) = -318
  • velocityY = 287.33 (rounded by println)
  • static_cast<int16_t>(velocityY) = 287

Theoretically if either condition in which addition became ineffective were reached, only one number should stop changing (unless they happened very close together, which seems unlikely), so I suspect this is something else.
I may run it again with an animation of some kind of animation in the corner to establish whether the program itself has ceased running.


Edit:
My test has confirmed that the Arduboy does actually stop at that point (i.e. the animation stops too), which most likely implies that it gets stuck in an infinite loop somehow.

F.Y.I. Adding 0.01f to a float will cease to be able to increase its value at 262144.0
(which is 2^16*4). I got the same result from both the Arduboy emulator and gcc on my 64bit Intel PC.

My code is (at this point in my journey) always a kind of hack that surprises me when it works. Because I don’t know the rules I approach things in weird ways. Pharap, the new example nails what I am looking for. I get it now. Mr. Scott I also enjoyed the tangent about the indefinite continuation offscreen. Thank the maker you guys all have Arduboys and sink your teeth into n00b questions like mine.

Max

2 Likes

HA! Playing around, how about this?

unsigned long elapsed = (millis()*.001); //seconds
if (arduboy.everyXFrames(60-(0.5*9.8*(pow(elapsed,2))))) //acceleration

There is no doubt a better representation, more efficient.

Only updating your position after a given number of frames have elapsed may not appear as smooth as updating every frame, especially as the speed increases. If you only change position every 4 frames, there are potentially 3 other frames in between where the position could have changed.

Continually varying the number of frames you wait, using everyXFrames(), could be problematic as well. everyXFrames() works with integers. E.g. you can’t have it return true every 2.75 frames.

You should also be aware of how everyXFrames() is implemented. The system keeps an overall running frame count and everyXFrames() just checks if this count can be be equally divided by the parameter you give it (i.e. if the remainder of the divide is 0). Therefore, it’s possible it will return true in less frames than expected after calling it with a value different than from the previous time it was called.

Overall, I think you’re better to work with time quantised by the frame rate, rather than trying to work in “real time” using the system clock.

This is actually forcing the result of millis() to be promoted to float, only for it to be cast back to an unsigned long.
If you used / 1000 instead then there would be no conversion to float,
which would probably save memory and presumably also be faster.

Typically the obvious way is actually the more efficient.

I’m not actually sure what you’re trying to do here.
0.5 * is obviously an alternative to / 2.0.
I’m presuming 9.8 is supposed to be Earth’s gravity?

I have no idea what pow(elapsed, 2) is supposed to be achieving though.
(For what it’s worth, that could just be elapsed * elapsed.
It’s an integer anyway, so you’re not going to gain anything from feeding it to the pow function.)

2 Likes

OK ok, nuts I guess. Yes I was messing with an attempt at average acceleration. You guys sure know a lot. My (apparently flawed) thought with everyXFrames was that if I had some function that was constantly drawing, say a rotating cube, drawing at a consistent rate, then I applied a gravity based formula to trigger that constant drawing, that I could get a realistic “spooling up” appearance in any constant-rate drawing routine. Like a turbine from a stop, accelerating to a set speed.

So…You wanted it to rotate?

What is the desired outcome for this? Like a prop spinning on a plane?

If you want to do that, you just apply a rotational acceleration formula for determining the next spin angle, at the same time that you do the linear acceleration calculation you’re using for making the cube fall.

However, note that at certain high spin speeds the cube may appear to stop or start spinning in reverse, due to the stroboscopic effect of a fixed frame rate. (Like wagon wheels and propellers in movies.)

Pharap, I meant to ask, where did adding an “f” come from?
ie: float positionY = -8.0f;

The f (or F) designates the literal value as type float. If you left off the f it would be a double (which would be implicitly converted to a float because that’s what positionY is). If you use the suffix l or L it designates the literal as having type long double.

https://en.cppreference.com/w/cpp/language/floating_literal

@Maxm,
When addressing a user using their username on this forum, it’s best to put an “at” sign as a prefix to the username. Write @Pharap instead of just Pharap. This will usually cause the user to receive an alert (email or otherwise) informing them that they have been referenced, so they may respond sooner.

For the same reason, it’s best to use the person’s username (again, with a leading @) instead of their real name, so for me it would be @MLXXXp instead of Scott. Doing this is not considered disrespectful in any way.

1 Like

In addition to what @MLXXXp said…

Usually it isn’t an issue if you omit the f, but if you were to use a double lieral in a calculation where all the other values were float you’d force them to be promoted to double which may not be what you intended and could potentially waste a bit of processing power or prevent certain optimisations (especially if the target is an x86 CPU).

On the Arduboy it doesn’t really matter because float and double actually have the same representation, but I write code for more than just Arduboy, hence my habits are tuned to avoid potential bugs.

1 Like

@MLXXXp it strikes me that this may be what I need: “Overall, I think you’re better to work with time quantised by the frame rate, rather than trying to work in “real time” using the system clock.”
Do you have an example that could show how this could be applied to acceleration?

Yes. @Pharap has already provided one for you.

1 Like