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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 52 additions & 50 deletions OZprivate/rawJS/OZTreeModule/src/position_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import {max, min} from './util/index';
let x_add = null;
let y_add = null;
let r_mult = null;
let intro_step_num = null;
let intro_sec_step_num = null;
let fly_start_time = null;
let global_anim_speed = 10; // set this to 15 for recoding smoother flight animations larger numbers mean slower
let length_intro = null;
let num_intro_steps = null;
/**
* The duration of this phase of the flight in seconds.
* Not necessarily the duration to the target as we may need to re-anchor after this phase and keep going.
*/
let fly_duration_s = null;
let more_flying_needed = null;
let flight_fps = 1000/60; // set this to 1000/500 for recording much slower flight animations that can then be sped up after the screen casting process is complete.
let original_flight_fps = 1000/60; // the frame rate used when global_anim_speed was calibrated
let into_node;
let pre_xp, pre_yp, pre_ws;
let fly_timer = null
let fly_frame_request_id = null

/** Update xvar, yvar and rvar of all targeted nodes and their direct children based on that of anchored nodes. */
function drawreg_target(node,x,y,r) {
// we assume that only those for whom graphref is true will call this routine
if (node.has_child) {
Expand Down Expand Up @@ -182,7 +185,7 @@ function get_v_horizon_by_arc(node, factor, x, y, r) {
let vxmax = (x+r*(node.arcx+node.arcr*factor));
let vxmin = (x+r*(node.arcx-node.arcr*factor));
let vymax = (y+r*(node.arcy+node.arcr*factor));
let vymin = (y+r*(node.arcy-node.arcr*factor)); // add for leaf points *1.305 doesn'fly_timer care about the leaf direction - could change this later perhaps
let vymin = (y+r*(node.arcy-node.arcr*factor)); // add for leaf points *1.305 doesn't care about the leaf direction - could change this later perhaps
return [vxmax, vxmin, vymax, vymin]
}

Expand Down Expand Up @@ -220,8 +223,7 @@ function perform_actual_fly(controller, into_node, speed=1, accel_type="linear",
pre_yp = tree_state.yp;
pre_ws = tree_state.ws;
get_xyr_target(controller.root, tree_state.xp, tree_state.yp, 220*tree_state.ws, into_node);
intro_step_num = 0;
intro_sec_step_num = 0;
fly_start_time = performance.now();
if(((r_mult>0.9999)&&(r_mult<1.00001))&&(x_add*x_add<1)&&(y_add*y_add<1)) {
// nothing to zoom to so better to do nothing and return false or it feels like a bug
if (typeof finalize_func === "function") {
Expand All @@ -244,9 +246,10 @@ function perform_actual_fly(controller, into_node, speed=1, accel_type="linear",
}
return true;
} else {
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

fly_frame_request_id = requestAnimationFrame(function () {
perform_fly_b2(controller, into_node, speed, accel_type, finalize_func, abrupt_func);
});
return true;
}
}
Expand All @@ -267,67 +270,66 @@ function perform_actual_fly(controller, into_node, speed=1, accel_type="linear",
* @param {func} abrupt_func is optional, and gives a function to call when fly is abrupted
*/
function perform_fly_b2(controller, into_node, speed, accel_type, finalize_func, abrupt_func) {
function pan_proportion(step, total) {
var x = step / total,
out = (Math.sin((x+0.25) * Math.PI*2) + 1) / 2;

return accel_type === 'accel' ? (x > 0.5 ? out : 0)
: accel_type === 'decel' ? 1 - (x < 0.5 ? out : 0)
: x;
const time_proportion = Math.max(0, Math.min((performance.now() - fly_start_time) / 1000 / fly_duration_s, 1));

function pan_proportion() {
var out = (Math.sin((time_proportion+0.25) * Math.PI*2) + 1) / 2;

return accel_type === 'accel' ? (time_proportion > 0.5 ? out : 0)
: accel_type === 'decel' ? 1 - (time_proportion < 0.5 ? out : 0)
: time_proportion;
}
function zoom_proportion(step, total) {
var x = step / total,
out = (Math.sin((x+1.5) * Math.PI) + 1) / 2;
function zoom_proportion() {
var out = (Math.sin((time_proportion+1.5) * Math.PI) + 1) / 2;

return accel_type === 'accel' ? out
: accel_type === 'decel' ? out
: x;
: time_proportion;
}

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 : time_proportion, time_proportion);
tree_state.set_action(r_mult > 1 ? "fly-in" : "fly-out");
controller.re_calc();
controller.reanchor();
controller.trigger_refresh_loop();

clearTimeout(fly_timer);
fly_timer = setTimeout(function () {
if (tree_state.flying) {
perform_actual_fly(controller, into_node, speed, 'linear', finalize_func, abrupt_func);
} else if (typeof abrupt_func === 'function') {
abrupt_func()
}
},1000.0/flight_fps);
} else if (!more_flying_needed && intro_step_num <num_intro_steps) {

if (tree_state.flying) {
perform_actual_fly(controller, into_node, speed, 'linear', finalize_func, abrupt_func);
} else if (typeof abrupt_func === 'function') {
abrupt_func()
}

} else {
//don't need to reanchor - this is more normal, and is smoother
intro_step_num++;
intro_sec_step_num++;
pan_zoom(
pan_proportion(intro_sec_step_num, num_intro_steps),
zoom_proportion(intro_sec_step_num, num_intro_steps)
pan_proportion(),
zoom_proportion()
);
tree_state.set_action(r_mult > 1 ? "fly-in" : "fly-out");
controller.re_calc();
controller.trigger_refresh_loop();

clearTimeout(fly_timer);
fly_timer = setTimeout(function () {
if (tree_state.flying) {
perform_fly_b2(controller, into_node, speed, accel_type, finalize_func, abrupt_func);
} else if (typeof abrupt_func === 'function') {
abrupt_func()
cancelAnimationFrame(fly_frame_request_id);

if (time_proportion < 1) {
fly_frame_request_id = requestAnimationFrame(function () {
if (tree_state.flying) {
perform_fly_b2(controller, into_node, speed, accel_type, finalize_func, abrupt_func);
} else if (typeof abrupt_func === 'function') {
abrupt_func()
}
});
} else {
tree_state.flying = false;
tree_state.set_action(null);
if (typeof finalize_func === "function") {
finalize_func()
}
},1000.0/flight_fps);
} else {
clearTimeout(fly_timer);
tree_state.flying = false;
tree_state.set_action(null);
if (typeof finalize_func === "function") {
finalize_func()
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion OZprivate/rawJS/OZTreeModule/tests/test_position_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ function test_cur_location(test, controller, node_latin_name, exp_xp, exp_yp, ex
exp_xp = round(exp_xp);
}


test('perform_actual_fly', function (test) {
global.requestAnimationFrame = (callback) => setTimeout(callback, 16);
global.cancelAnimationFrame = clearTimeout;
var nodes = {};

return populate_factory().then((factory) => {
Expand Down Expand Up @@ -151,6 +152,9 @@ test('perform_actual_fly', function (test) {


test.onFinish(function() {

global.requestAnimationFrame = undefined;
global.cancelAnimationFrame = undefined;
// NB: Something data_repo includes in is holding node open.
// Can't find it so force our tests to end.
process.exit(0)
Expand Down
Loading