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

Smooth flights #925

Merged
merged 2 commits into from
Dec 13, 2024
Merged

Smooth flights #925

merged 2 commits into from
Dec 13, 2024

Conversation

jaredkhan
Copy link
Collaborator

@jaredkhan jaredkhan commented Nov 30, 2024

Fixes #910

Before:
https://github.com/user-attachments/assets/7e1d2d55-ac31-44c6-82b4-b9c2c312f9c6

After:
https://github.com/user-attachments/assets/63a520af-c209-4502-8d74-d105ffe137c2

Essentially replacing the use of setTimeout with the use of requestAnimationFrame in a way that checks the elapsed time and does the movement propoertionally based on that

@jaredkhan jaredkhan force-pushed the smooth-flights branch 2 times, most recently from f5a7f42 to 97cd615 Compare November 30, 2024 01:02
@davidebbo
Copy link
Collaborator

Tests failing with:

ReferenceError: localStorage is not defined
    at progressWipe (/home/david/www-dev/web2py/applications/OZtree/OZprivate/rawJS/OZTreeModule/src/tour/handler/TsProgress.js:97:17)

Maybe something needs to be mocked?

@jaredkhan
Copy link
Collaborator Author

Yeah the localStorage thing is there in main too actually, the actual failure was due to requestAnimationFrame not being available. Fixed now.

@davidebbo
Copy link
Collaborator

Thanks, it passes now for me. But FWIW, it's not failing for me on main.

@jaredkhan
Copy link
Collaborator Author

No I'm not saying it fails on main , just that the error you quoted is there when running the tests:
(ReferenceError: localStorage is not defined...)

@davidebbo
Copy link
Collaborator

No I'm not saying it fails on main , just that the error you quoted is there when running the tests:
(ReferenceError: localStorage is not defined...)

Ah I see!

@davidebbo
Copy link
Collaborator

Tried it, definitely looks smoother!

@jaredkhan jaredkhan marked this pull request as ready for review December 1, 2024 21:04
}

if (more_flying_needed) {
//need to reanchor, this sometimes causes jerkiness
//also we may not know how many steps we will need to take
// NB: Approximate accel/decel by not panning at all, let the final non-reanchor step handle it
pan_zoom(accel_type === 'accel' || accel_type === 'decel'? 0 : 1/num_intro_steps, 1/num_intro_steps);
pan_zoom(accel_type === 'accel' || accel_type === 'decel'? 0 : clamped_time_proportion, clamped_time_proportion);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, we do this thing of going back to perform_actual_fly every frame when we're going a long way. This means that we'd ideally want to be more careful about capturing all non-trivial work between when we start the timer (fly_start_time) and when we measure the time proportion. So ideally we'd get all the re_calc/reanchor stuff inside that interval.

Buuut I wasn't totally sure why we do the re-anchoring this frequently anyway. Since we're limiting the r_mult range to 1M either way might it not make sense to just re-anchor at the end of this planned segment of the flight rather than every frame?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main thing to avoid is chunking up the flight into r_mult chunks, and the final deceleration phase being really short, because it's only just beyond the r_mult limit.

Beyond that, I think the constant re-anchoring is just what was easiest at the time. Removing it definitely makes sense.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that makes sense. Perhaps a sensible scheme would be to chop the interval in half if it's greater than the interval

@jaredkhan jaredkhan requested a review from lentinj December 1, 2024 21:13
Copy link
Collaborator

@lentinj lentinj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is all really good stuff, making the code both simpler and better.

My main criticism is that it makes the flights a bit robotic. Not from any bugs in this commit, but rather the increased framerate makes shortcomings in the flight code more obvious.

I think it's worth playing with the pan_proportion() / zoom_proportion() easing functions to make them less subtle, maybe using the equivalent of the bezier curve "ease-in-out" uses.

The pause at the common ancestor of a flight is also quite noticeable now, but as @jaredkhan already notes, we should plan more of the flight in advance, which would help here too.

OZprivate/rawJS/OZTreeModule/src/position_helper.js Outdated Show resolved Hide resolved
}

if (more_flying_needed) {
//need to reanchor, this sometimes causes jerkiness
//also we may not know how many steps we will need to take
// NB: Approximate accel/decel by not panning at all, let the final non-reanchor step handle it
pan_zoom(accel_type === 'accel' || accel_type === 'decel'? 0 : 1/num_intro_steps, 1/num_intro_steps);
pan_zoom(accel_type === 'accel' || accel_type === 'decel'? 0 : clamped_time_proportion, clamped_time_proportion);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main thing to avoid is chunking up the flight into r_mult chunks, and the final deceleration phase being really short, because it's only just beyond the r_mult limit.

Beyond that, I think the constant re-anchoring is just what was easiest at the time. Removing it definitely makes sense.

length_intro = Math.abs(Math.log(r_mult))*global_anim_speed;
num_intro_steps = Math.max(Math.floor(length_intro),12)/speed;
perform_fly_b2(controller, into_node, speed, accel_type, finalize_func, abrupt_func);
fly_duration_s = Math.max(Math.abs(Math.log(r_mult)) * global_anim_speed, 12) / speed / original_flight_fps;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whilst it all looks correct to me, I think we have too many magic numbers in this expression at this point. global_anim_speed as a tweakable value is kinda pointless after this. The point was to fake smooth flights for screencasts, and now we just have smooth flights all the time :)

Does doing something like the following make sense?

fly_duration_s = Math.max(Math.abs(Math.log(r_mult)), 12 / global_anim_speed) * global_anim_speed
    / speed / original_flight_fps;

... and then ...

const original_flight_fps = (1000/60) / 10;  // 10 calibrates frame rate against animation speed values
fly_duration_s = Math.max(Math.abs(Math.log(r_mult)), 1.2)
    / (speed * original_flight_fps);

I also think that Math.max(x, 1.2) is too subtle to be worth it, and only really serves to accentuate the shuffling backwards to the common ancestor when you're basically already on it. My preference would be to bin it, but maybe @jrosindell should weigh in at this point.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think the units of global_anim_speed and speed aren't very obvious (some mystery constant factor of 1/scale-doubling time).

That said, I don't think the Math.max is that subtle, the current easing looks quite abrupt at low durations because what you end up with looks like high acceleration.

river_turtle_with_min.mov

I think I'll leave this as it is tbh, not seeing a clear benefit of reshuffling stuff, certainly if we call it original_flight_fps then it should be in units of frames per second and if we call it something else and stuff the factor of 10 in there then it's kind of just another bizzarely-united value floating around.

river_turtle_no_min.mov

@lentinj
Copy link
Collaborator

lentinj commented Dec 9, 2024

Worth noting I've only tested this at 60Hz, both on my desktop and my (ageing) phone. It'd be interesting to see if we can keep up with 120Hz, or close enough that it doesn't appear choppy.

@jaredkhan
Copy link
Collaborator Author

Yeah I've been running it on a 120Hz display sometimes. It more or less keeps up!
There's a couple of long-running frames which are, as usual, dominated by text rendering, but nothing particularly bothersome to my eye.
image

@hyanwong
Copy link
Member

Is this ready to merge, or are there some of @lentinj 's suggestions to add / investigate?

@lentinj lentinj merged commit 7df7368 into OneZoom:main Dec 13, 2024
1 check passed
@lentinj lentinj mentioned this pull request Dec 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Animation frame rates
4 participants