Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frame independent earthquakes #278

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open

Frame independent earthquakes #278

wants to merge 10 commits into from

Conversation

bsxf-47
Copy link
Contributor

@bsxf-47 bsxf-47 commented May 30, 2023

Attempted to replace the calculation with random values (looks about the same) because the old algorithm didn't really work when trying to update only once in a few milliseconds. The update period for intensity of 10 (scripted cobweb earthquake) is at 10ms but new values are chosen at the next function call so it looks fairly close even at 30fps with frame time of ~33ms.

Fixes: Bug #1554

Included are three save files that contain examples of quakes:
Scripted cobweb quake - AFAIK strongest in the game, short period
Giant worm - another scripted quake when the worm moves, weaker, longer period
Ylside bunker - one of the strongest hits from NPCs, long period

Let me know what you think of the look and feel.

saves.zip

@bsxf-47 bsxf-47 changed the title Independent earthquakes Frame independent earthquakes May 30, 2023
@dscharrer
Copy link
Member

I'll do some more detailed comparisons with the original effect once I have more time but this looks great so far.

@dscharrer
Copy link
Member

I have looked at it in more detail and your changes and am a bit uncomfortable with changing the effect even at low frame-rates, in particular removing the sine.

Here are the quake commands used for the effects - I have left out damage because it doesn't have one set of parameters but randomizes them. Note that the third parameter really is a frequency rather than a period in the original - the parameter was just misnamed:

Location quake intensity duration frequency original sine period your update interval
Spiderweb quake 700 5300 10 ~63ms 10ms
Giant Worm quake 300 1000 100 ~6ms 18ms
Explosion Spell quake 300 2000 400 ~1ms 20ms

For the original effect it picks one new value per frame (so let's say every ~33ms for 30 FPS) but limits the random values by a sine wave based on the frequency. This sine has a period of

original sine period = 200 * pi / frequency ms

which is only 63ms for the "slowest" quake (spiderweb with frequency 10) but its still noticeable. It's true that for the giant worm and weapon hit the frequency is high enough that it is just a second (shitty) random but for the spiderweb it does add a bit of periodicity instead of just completely random shaking. Aliasing may also make the sampled value appear at a lower frequency than the original sine, not sure if that is happening here.

Your effect on the other hand picks a new random offset with constant maximum at a fixed interval of

your update interval = 2 * frequency * (1 - frequency / (frequency + 10)) ms

You also jump straight to the chosen value (since timeFromLastUpdate doesn't get reset) for one frame and then interpolate from 0 to the chosen random value for the remainder of the interval.

Wouldn't it make more sense to keep the original effect but sample it at a fixed rate (e.g. 30 FPS) and then interpolate between two successive random values? Something like this:

GameDuration updateInterval = 1s / 30.f;
GameDuration timeFromLastUpdate = g_gameTime.now() - QuakeFx.lastChange;
if(timeFromLastUpdate >= updateInterval || QuakeFx.lastChange == 0) {
	float periodicity = 0;
	if(QuakeFx.period > 0.f) {
		periodicity = timeWaveSin(g_gameTime.now(), 200ms / QuakeFx.period * glm::pi<float>());
	}
	
	float itmod = 1.f - (elapsed / QuakeFx.duration);
	float truepower = periodicity * QuakeFx.intensity * itmod * 0.01f;
	float halfpower = truepower * .5f;
	QuakeFx.lastPos = QuakeFx.targetPos;
	QuakeFx.lastAngle = QuakeFx.targetAngle;
	QuakeFx.targetAngle.setPitch(Random::getf(-1.f, 1.f) * halfpower);
	QuakeFx.targetAngle.setYaw(Random::getf(-1.f, 1.f) * halfpower);
	QuakeFx.targetAngle.setRoll(Random::getf(-1.f, 1.f) * halfpower);
	QuakeFx.targetPos = arx::randomVec(-1.f, 1.f) * halfpower;
	QuakeFx.lastChange = g_gameTime.now();
	timeFromLastUpdate = 0; // Make sure that the full random offest is used for one frame
}

float d = timeFromLastUpdate / updateInterval;

cam->m_pos += glm::mix(QuakeFx.lastPos, QuakeFx.targetPos, d);
cam->angle += interpolate(QuakeFx.lastAngle, QuakeFx.targetAngle, d);

This still isn't perfect because for frame-rates close to 30 we have very irregular updates and for frame-rates close to (a multiple of) the sine period the sample of the sine wave will have heavy aliasing to the point that it might look more like a constant. I'm not sure yet how to improve that.

@dscharrer dscharrer force-pushed the master branch 5 times, most recently from 391b7f7 to 3d8a7e6 Compare May 30, 2024 12:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants