Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Versioning](https://semver.org/spec/v2.0.0.html) for `libnopegl`.
- `ngl-export` tool to export videos for all the scenes from a given script
- Path and text rendering can now control the position of the outline (inner,
centered, outer, or anything in between) through the `outline_pos` parameter
- `bezier_cubic` easing

### Fixed
- Crash when using resizable RTTs with time ranges
Expand Down
4 changes: 4 additions & 0 deletions libnopegl/nodes.specs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@
{
"name": "back_out_in",
"desc": "combination of `back_out` then `back_in`"
},
{
"name": "bezier_cubic",
"desc": "cubic bezier curve"
}
],
"memory_layout": [
Expand Down
174 changes: 174 additions & 0 deletions libnopegl/src/node_animkeyframe.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/*
* Copyright 2024 Matthieu Bouron <[email protected]>
* Copyright 2019 The Android Open Source Project
* Copyright 2016-2022 GoPro Inc.
*
* Licensed to the Apache Software Foundation (ASF) under one
Expand All @@ -24,6 +26,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <float.h>

#include "internal.h"
#include "log.h"
Expand Down Expand Up @@ -64,6 +67,7 @@ static const struct param_choices easing_choices = {
{"back_out", EASING_BACK_OUT, .desc=NGLI_DOCSTRING("overstep target value and smoothly converge back to it")},
{"back_in_out", EASING_BACK_IN_OUT, .desc=NGLI_DOCSTRING("combination of `back_in` then `back_out`")},
{"back_out_in", EASING_BACK_OUT_IN, .desc=NGLI_DOCSTRING("combination of `back_out` then `back_in`")},
{"bezier_cubic", EASING_BEZIER_CUBIC, .desc=NGLI_DOCSTRING("cubic bezier curve")},
{NULL}
}
};
Expand Down Expand Up @@ -319,6 +323,175 @@ static easing_type back_derivative(easing_type t, easing_type s)
DECLARE_EASINGS(back, , back_func(x, PARAM(0, 1.70158)), TRANSFORM)
DECLARE_EASINGS(back, _derivative, back_derivative(x, PARAM(0, 1.70158)), DERIVATIVE)

/* Cubic Bezier */

static int double_close_to_zero(double value)
{
return fabs(value) < FLT_EPSILON;
}

static float clamp_root(float r)
{
const float s = NGLI_CLAMP(r, 0.f, 1.f);
return fabsf(s - r) > FLT_EPSILON ? NAN : s;
Copy link
Contributor

@ubitux ubitux May 13, 2025

Choose a reason for hiding this comment

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

FLT_EPSILON is, as far as I can tell, not a very good threshold choice as it's probably going to filter out all sorts of cases where the root is close to 0 or 1: it's a very restrictive value, and at the same time not 100%. A more arbitrary 1e-6 (that can be understood as a "precision" rounding) will likely do a better job.

}

/*
* Fast cbrt adapted from the Android Compose library.
*
* See: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt;l=160
*/
static float fast_cbrt(float x)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use the native cbrtf? (you can also drop the android copyright then)

{
union { float f32; uint32_t u32; } v = { .f32 = x };
v.u32 = v.u32 & 0x1ffffffffL;
v.u32 = 0x2a510554 + (v.u32 / 3);

float estimate = v.f32;
estimate -= (estimate - x / (estimate * estimate)) * (1.0f / 3.0f);
estimate -= (estimate - x / (estimate * estimate)) * (1.0f / 3.0f);
return estimate;
}

/*
* Find the first cubic root using Cardano's algorithm.
*
* See: https://pomax.github.io/bezierinfo/#yforx
*/
static float find_first_cubic_root(float p0, float p1, float p2, float p3)
{
double a = 3.0 * (p0 - 2.0 * p1 + p2);
double b = 3.0 * (p1 - p0);
double c = p0;
double d = -p0 + 3.0 * (p1 - p2) + p3;
Comment on lines +363 to +366
Copy link
Contributor

@ubitux ubitux May 13, 2025

Choose a reason for hiding this comment

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

So I'm assuming this is the cubic points being converted to polynomial coefficients, but the value are weirdly shuffled. You made your polynomial $dx^3+ax^2+bx+c$, which I'm assuming you did because you're making it monic later down the line and reusing a/b/c naming. I think it would make sense to keep it as $ax^3+bx^2+cx+d$ for clarity, then pass it down to another function as $f(\frac{b}{a}, \frac{c}{a}, \frac{d}{a})$ where you can name again the parameters a, b and c.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I implemented it so it matches the ref mentioned in the comment, see: https://pomax.github.io/bezierinfo/#yforx


if (double_close_to_zero(d)) {
if (double_close_to_zero(a)) {
if (double_close_to_zero(b)) {
return NAN;
}
return clamp_root((float) (-c / b));
} else {
double q = sqrt(b * b - 4.0 * a * c);
Copy link
Contributor

@ubitux ubitux May 13, 2025

Choose a reason for hiding this comment

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

This is missing a check if the $b^2-4ac &lt; 0$

double a2 = 2.0 * a;

float root = clamp_root(((float) ((q - b) / a2)));
if (!isnan(root))
return root;

return clamp_root(((float) ((-b - q) / a2)));
Comment on lines +375 to +382
Copy link
Contributor

@ubitux ubitux May 13, 2025

Choose a reason for hiding this comment

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

Non-blocking: there is a simpler quadratic formula which we're using in distmap where you find a middle point $m=-\frac{b}{2a}$ that you offset by $±\sqrt{\Delta}$ where $\Delta=m^2-\frac{c}{a}$ to get each root.

Since there are less subtractions, I also believe it leads to a lower risk of catastrophic cancellation (to be verified though).

}
}

a /= d;
b /= d;
c /= d;

double o3 = (3.0 * b - a * a) / 9.0;
double q2 = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0;
double discriminant = q2 * q2 + o3 * o3 * o3;
double a3 = a / 3.0;

// Three possible real roots
if (discriminant < 0.0) {
double mp33 = -(o3 * o3 * o3);
double r = sqrt(mp33);
double t = -q2 / r;
double cos_phi = NGLI_CLAMP(t, -1.0, 1.0);
double phi = acos(cos_phi);
double t1 = 2.0 * fast_cbrt((float)r);

float root = clamp_root((float)(t1 * cos(phi / 3.0) - a3));
if (!isnan(root))
return root;

root = clamp_root((float)(t1 * cos((phi + TAU_F64) / 3.0) - a3));
if (!isnan(root))
return root;

return clamp_root((float)(t1 * cos((phi + 2.0 * TAU_F64) / 3.0) - a3));
}

// Three real roots, but two of them are equal
if (discriminant == 0.0) {
float u1 = -fast_cbrt((float)q2);

float root = clamp_root((float)(2.0 * u1 - a3));
if (!isnan(root))
return root;

return clamp_root((float)(-u1 - a3));
}

// One real root, two complex roots
double sd = sqrt(discriminant);
double u1 = fast_cbrt((float)(-q2 + sd));
double v1 = fast_cbrt((float)(q2 + sd));

return clamp_root((float)(u1 - v1 - a3));
Copy link
Contributor

@ubitux ubitux May 13, 2025

Choose a reason for hiding this comment

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

Note that in my experience the analytic cubic is filled with numerical instability and I wouldn't recommend it... I ended up dropping it from the distmap for a more stable iterative algorithm.

}

static double evaluate_cubic(double p1, double p2, double t)
{
double a = 1.0 / 3.0 + (p1 - p2);
double b = (p2 - 2.0 * p1);
double c = p1;
return 3.0 * ((a * t + b) * t + c) * t;
Comment on lines +436 to +439
Copy link
Contributor

@ubitux ubitux May 13, 2025

Choose a reason for hiding this comment

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

I would recommend computing and caching the polynomial for faster evaluation.

}

static easing_type bezier_cubic(easing_type t, size_t args_nb, const easing_type *args)
{
if (t < 0.f || t > 1.f)
return t;

const easing_type a = PARAM(0, 0.0);
const easing_type b = PARAM(1, 0.0);
const easing_type c = PARAM(2, 1.0);
const easing_type d = PARAM(3, 1.0);
Comment on lines +447 to +450
Copy link
Contributor

@ubitux ubitux May 15, 2025

Choose a reason for hiding this comment

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

So here is something: with easing, the starting and ending points are always respectively (0,0) and (1,1). If you need some sort of offsetting, you can use easing_start_offset and easing_end_offset.

Next, you can't have the curve going backward, which means $b_x ≤ c_x$ must be verified (maybe this is already the case due to the points being in a single dimension).

All these constraints may also make the solver and friends way more convenient and may avoid a bunch of tests. You also won't need to extend the number of parameter from 2 to 4.


const easing_type v = NGLI_MAX(t, FLT_EPSILON);
Copy link
Contributor

@ubitux ubitux May 15, 2025

Choose a reason for hiding this comment

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

why is $t=0$ not allowed? It should just be the starting point

const float p0 = (float)(0.0 - v);
const float p1 = (float)( a - v);
const float p2 = (float)( c - v);
const float p3 = (float)(1.0 - v);

easing_type r = find_first_cubic_root(p0, p1, p2, p3);
if (isnan(r)) {
/* No root found */
return 0.0;
}

return evaluate_cubic(b, d, r);
}

static easing_type bezier_cubic_derivative(easing_type t, size_t args_nb, const easing_type *args)
{
if (t < 0.f || t > 1.f)
return 1.0;

const easing_type a = PARAM(0, 0.0);
const easing_type b = PARAM(1, 0.0);
const easing_type c = PARAM(2, 1.0);
const easing_type d = PARAM(3, 1.0);

const double da = 3.0 * (b - a);
const double db = 3.0 * (c - b);
const double dc = 3.0 * (d - c);

const easing_type v = NGLI_MAX(t, FLT_EPSILON);
const float dp0 = (float)(0.0 - v);
const float dp1 = (float)( da - v);
const float dp2 = (float)( dc - v);

easing_type r = find_first_cubic_root(dp0, dp1, dp2, 0.f);
if (isnan(r)) {
/* No root found */
return 1.0;
}

return evaluate_cubic(db, 0.0, r);
}

static const struct {
easing_function function;
easing_function derivative;
Expand Down Expand Up @@ -365,6 +538,7 @@ static const struct {
[EASING_BACK_OUT] = {back_out, back_out_derivative, NULL},
[EASING_BACK_IN_OUT] = {back_in_out, back_in_out_derivative, NULL},
[EASING_BACK_OUT_IN] = {back_out_in, back_out_in_derivative, NULL},
[EASING_BEZIER_CUBIC] = {bezier_cubic, bezier_cubic_derivative, NULL},
};

static int check_offsets(double x0, double x1)
Expand Down
1 change: 1 addition & 0 deletions libnopegl/src/node_animkeyframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ enum easing_id {
EASING_BACK_OUT,
EASING_BACK_IN_OUT,
EASING_BACK_OUT_IN,
EASING_BEZIER_CUBIC,
};

typedef double easing_type;
Expand Down
25 changes: 13 additions & 12 deletions python/pynopegl-utils/pynopegl_utils/examples/animations.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,18 +128,19 @@ def _get_easing_node(cfg: ngl.SceneCfg, easing, curve_zoom, color_program, nb_po

_easing_specs = (
# fmt: off
("linear", 0, 1.),
("quadratic", 3, 1.),
("cubic", 3, 1.),
("quartic", 3, 1.),
("quintic", 3, 1.),
("power:7.3", 3, 1.),
("sinus", 3, 1.),
("exp", 3, 1.),
("circular", 3, 1.),
("bounce", 1, 1.),
("elastic", 1, 0.5),
("back", 3, 0.7),
("linear", 0, 1.),
("quadratic", 3, 1.),
("cubic", 3, 1.),
("quartic", 3, 1.),
("quintic", 3, 1.),
("power:7.3", 3, 1.),
("sinus", 3, 1.),
("exp", 3, 1.),
("circular", 3, 1.),
("bounce", 1, 1.),
("elastic", 1, 0.5),
("back", 3, 0.7),
("bezier_cubic", 0, 1.),
# fmt: on
)

Expand Down
6 changes: 3 additions & 3 deletions python/pynopegl/_pynopegl.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -407,13 +407,13 @@ ANIM_EVALUATE, ANIM_DERIVATE, ANIM_SOLVE = range(3)


def animate(const char *name, double src, args, offsets, mode):
cdef double c_args[2]
cdef double c_args[4]
cdef double *c_args_param = NULL
cdef size_t nb_args = 0
if args is not None:
nb_args = len(args)
if nb_args > 2:
raise Exception("Easings do not support more than 2 arguments")
if nb_args > 4:
raise Exception("Easings do not support more than 4 arguments")
for i, arg in enumerate(args):
c_args[i] = arg
c_args_param = c_args
Expand Down
34 changes: 21 additions & 13 deletions tests/anim.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#
# Copyright 2024 Matthieu Bouron <[email protected]>
# Copyright 2020-2022 GoPro Inc.
#
# Licensed to the Apache Software Foundation (ASF) under one
Expand Down Expand Up @@ -39,19 +40,21 @@ def _easing_join(easing, args):

_easing_specs = (
# fmt: off
("linear", 0),
("quadratic", 3),
("cubic", 3),
("quartic", 3),
("quintic", 3),
("power:7.3", 3),
("sinus", 3),
("exp", 3),
("circular", 3),
("bounce", 1),
("elastic", 1),
("elastic:1.5:1.2", 1),
("back", 3),
("linear", 0),
("quadratic", 3),
("cubic", 3),
("quartic", 3),
("quintic", 3),
("power:7.3", 3),
("sinus", 3),
("exp", 3),
("circular", 3),
("bounce", 1),
("elastic", 1),
("elastic:1.5:1.2", 1),
("back", 3),
("bezier_cubic", 0),
("bezier_cubic:0.45:0:0.58:1", 0),
# fmt: on
)

Expand Down Expand Up @@ -111,8 +114,13 @@ def _test_derivative_approximations(nb_points=20):
times = [i * scale for i in range(nb_points + 1)]
max_err = 0.0005

# Unsupported easings (due to lack of precision)
unsupported_easings = ["bezier_cubic"]

for easing in _easing_list:
easing_name, easing_args = _easing_split(easing)
if easing_name in unsupported_easings:
continue
for offsets in _offsets:
out_vals = [ngl.easing_derivate(easing_name, t, easing_args, offsets) for t in times]
approx_vals = [_approx_derivative(easing_name, t, easing_args, offsets) for t in times]
Expand Down
8 changes: 8 additions & 0 deletions tests/refs/anim_derivative_api.ref
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,11 @@ back_out_in: 4.701580 1.704243 0.030129 -0.320763 -0.320763 0.030129 1.704243 4.
back_out_in: 7.308304 3.830916 1.361391 -0.100270 -0.554067 -0.000000 -0.554067 -0.100270
back_out_in: -0.100270 -0.554067 0.000000 -0.554067 -0.100270 1.361391 3.830916 7.308304
back_out_in: 0.259706 1.250990 1.389886 0.676393 0.676393 1.389886 1.250990 0.259706
bezier_cubic: 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
bezier_cubic: 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
bezier_cubic: 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
bezier_cubic: 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
bezier_cubic: 1.000000 1.000000 1.000000 0.674910 1.000000 0.999999 0.712637 1.000000
bezier_cubic: 0.868451 0.868451 0.868451 0.603694 0.868451 0.868451 0.868451 0.586249
bezier_cubic: 0.587274 0.844829 0.844829 0.844829 0.570303 0.844829 0.844828 0.844829
bezier_cubic: 0.438157 0.431718 0.426499 0.422690 0.420486 0.420083 0.421683 0.425495
8 changes: 8 additions & 0 deletions tests/refs/anim_forward_api.ref
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,11 @@ back_out_in: 0.000000 0.441806 0.549937 0.513425 0.486575 0.450063 0.558194 1.00
back_out_in: 0.000000 0.783660 1.142541 1.220623 1.161886 1.110311 1.058737 1.000000
back_out_in: 0.000000 -0.058737 -0.110311 -0.161886 -0.220623 -0.142541 0.216340 1.000000
back_out_in: -0.000000 0.118054 0.316836 0.474575 0.525425 0.683164 0.881946 1.000000
bezier_cubic: 0.000000 0.142857 0.285714 0.428571 0.571429 0.714286 0.857143 1.000000
bezier_cubic: 0.000000 0.142857 0.285714 0.428571 0.571428 0.714286 0.857143 1.000000
bezier_cubic: 0.000000 0.142857 0.285714 0.428571 0.571429 0.714286 0.857143 1.000000
bezier_cubic: 0.000000 0.142857 0.285714 0.428571 0.571429 0.714286 0.857143 1.000000
bezier_cubic: 0.000000 0.036303 0.154749 0.357167 0.605809 0.824576 0.958392 1.000000
bezier_cubic: 0.000000 0.021578 0.090797 0.212684 0.385849 0.595646 0.811923 1.000000
bezier_cubic: 0.000000 0.168455 0.372545 0.582940 0.765901 0.899058 0.975914 1.000000
bezier_cubic: 0.000000 0.118417 0.255865 0.407468 0.566078 0.723122 0.870087 1.000000
8 changes: 4 additions & 4 deletions tests/refs/anim_forward_float.ref
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
off0: 0.757954 0.420572 0.258917 0.511275 0.404934 0.783799 0.303313 0.476597 0.583382 0.908113 0.504687 0.281838 0.755804 0.844422 0.618369 0.618369
off1: 0.757954 0.420572 0.258917 0.511275 0.404934 0.783799 0.303313 0.476597 0.583382 0.908113 0.504687 0.281838 0.755804 0.844422 0.618369 0.618369
off2: 0.757954 0.420572 0.258917 0.511275 0.404934 0.783799 0.303313 0.476597 0.583382 0.908113 0.504687 0.281838 0.755804 0.844422 0.618369 0.618369
off3: 0.757954 0.420572 0.258917 0.511275 0.404934 0.783799 0.303313 0.476597 0.583382 0.908113 0.504687 0.281838 0.755804 0.844422 0.618369 0.618369
off0: 0.757954 0.420572 0.258917 0.511275 0.404934 0.783799 0.303313 0.476597 0.583382 0.908113 0.504687 0.281838 0.755804 0.618369 0.250506 0.844422 0.909746 0.909746
off1: 0.757954 0.420572 0.258917 0.511275 0.404934 0.783799 0.303313 0.476597 0.583382 0.908113 0.504687 0.281838 0.755804 0.618369 0.250506 0.844422 0.909746 0.909746
off2: 0.757954 0.420572 0.258917 0.511275 0.404934 0.783799 0.303313 0.476597 0.583382 0.908113 0.504687 0.281838 0.755804 0.618369 0.250506 0.844422 0.909746 0.909746
off3: 0.757954 0.420572 0.258917 0.511275 0.404934 0.783799 0.303313 0.476597 0.583382 0.908113 0.504687 0.281838 0.755804 0.618369 0.250506 0.844422 0.909746 0.909746
8 changes: 4 additions & 4 deletions tests/refs/anim_forward_quat.ref
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
off0: 0.480603 0.380642 0.736778 0.285117 0.371384 0.454595 0.707639 0.393273 0.269236 0.722009 0.590719 0.239305 0.503564 0.543992 0.448472 0.499368 0.224733 0.528837 0.651300 0.495616 0.529613 0.112959 0.487020 0.685246 0.551146 0.583499 0.287951 0.522351 0.260492 0.805028 0.548699 0.014042 0.535866 0.296950 0.614150 0.497483 0.001143 0.493578 0.867603 0.243911 0.294179 0.787425 0.172839 0.513368 0.175965 0.713504 0.592298 0.330351 0.072327 0.287755 0.456681 0.838695 0.682349 0.612477 0.339850 0.209222 0.109058 0.551267 0.706561 0.547441 0.109058 0.551267 0.706561 0.547441
off1: 0.480603 0.380642 0.736778 0.285117 0.371384 0.454595 0.707639 0.393273 0.269236 0.722009 0.590719 0.239305 0.503564 0.543992 0.448472 0.499368 0.224733 0.528837 0.651300 0.495616 0.529613 0.112959 0.487020 0.685246 0.551146 0.583499 0.287951 0.522351 0.260492 0.805028 0.548699 0.014042 0.535866 0.296950 0.614150 0.497483 0.001143 0.493578 0.867603 0.243911 0.294179 0.787425 0.172839 0.513368 0.175965 0.713504 0.592298 0.330351 0.072327 0.287755 0.456681 0.838695 0.682349 0.612477 0.339850 0.209222 0.109058 0.551267 0.706561 0.547441 0.109058 0.551267 0.706561 0.547441
off2: 0.480603 0.380642 0.736778 0.285117 0.371384 0.454595 0.707639 0.393273 0.269236 0.722009 0.590719 0.239305 0.503564 0.543992 0.448472 0.499368 0.224733 0.528837 0.651300 0.495616 0.529613 0.112959 0.487020 0.685246 0.551146 0.583499 0.287951 0.522351 0.260492 0.805028 0.548699 0.014042 0.535866 0.296950 0.614150 0.497483 0.001143 0.493578 0.867603 0.243911 0.294179 0.787425 0.172839 0.513368 0.175965 0.713504 0.592298 0.330351 0.072327 0.287755 0.456681 0.838695 0.682349 0.612477 0.339850 0.209222 0.109058 0.551267 0.706561 0.547441 0.109058 0.551267 0.706561 0.547441
off3: 0.480603 0.380642 0.736778 0.285117 0.371384 0.454595 0.707639 0.393273 0.269236 0.722009 0.590719 0.239305 0.503564 0.543992 0.448472 0.499368 0.224733 0.528837 0.651300 0.495616 0.529613 0.112959 0.487020 0.685246 0.551146 0.583499 0.287951 0.522351 0.260492 0.805028 0.548699 0.014042 0.535866 0.296950 0.614150 0.497483 0.001143 0.493578 0.867603 0.243911 0.294179 0.787425 0.172839 0.513368 0.175965 0.713504 0.592298 0.330351 0.072327 0.287755 0.456681 0.838695 0.682349 0.612477 0.339850 0.209222 0.109058 0.551267 0.706561 0.547441 0.109058 0.551267 0.706561 0.547441
off0: 0.480603 0.380642 0.736778 0.285117 0.371384 0.454595 0.707639 0.393273 0.269236 0.722009 0.590719 0.239305 0.503564 0.543992 0.448472 0.499368 0.224733 0.528837 0.651300 0.495616 0.529613 0.112959 0.487020 0.685246 0.551146 0.583499 0.287951 0.522351 0.260492 0.805028 0.548699 0.014042 0.535866 0.296950 0.614150 0.497483 0.001143 0.493578 0.867603 0.243911 0.294179 0.787425 0.172839 0.513368 0.175965 0.713504 0.592298 0.330351 0.072327 0.287755 0.456681 0.838695 0.103294 0.522133 0.669220 0.518509 0.543210 0.360343 0.642833 0.402295 0.682349 0.612477 0.339850 0.209222 0.587617 0.444989 0.596287 0.384901 0.587617 0.444989 0.596287 0.384901
off1: 0.480603 0.380642 0.736778 0.285117 0.371384 0.454595 0.707639 0.393273 0.269236 0.722009 0.590719 0.239305 0.503564 0.543992 0.448472 0.499368 0.224733 0.528837 0.651300 0.495616 0.529613 0.112959 0.487020 0.685246 0.551146 0.583499 0.287951 0.522351 0.260492 0.805028 0.548699 0.014042 0.535866 0.296950 0.614150 0.497483 0.001143 0.493578 0.867603 0.243911 0.294179 0.787425 0.172839 0.513368 0.175965 0.713504 0.592298 0.330351 0.072327 0.287755 0.456681 0.838695 0.103294 0.522133 0.669220 0.518509 0.543210 0.360343 0.642833 0.402295 0.682349 0.612477 0.339850 0.209222 0.587617 0.444989 0.596287 0.384901 0.587617 0.444989 0.596287 0.384901
off2: 0.480603 0.380642 0.736778 0.285117 0.371384 0.454595 0.707639 0.393273 0.269236 0.722009 0.590719 0.239305 0.503564 0.543992 0.448472 0.499368 0.224733 0.528837 0.651300 0.495616 0.529613 0.112959 0.487020 0.685246 0.551146 0.583499 0.287951 0.522351 0.260492 0.805028 0.548699 0.014042 0.535866 0.296950 0.614150 0.497483 0.001143 0.493578 0.867603 0.243911 0.294179 0.787425 0.172839 0.513368 0.175965 0.713504 0.592298 0.330351 0.072327 0.287755 0.456681 0.838695 0.103294 0.522133 0.669220 0.518509 0.543210 0.360343 0.642833 0.402295 0.682349 0.612477 0.339850 0.209222 0.587617 0.444989 0.596287 0.384901 0.587617 0.444989 0.596287 0.384901
off3: 0.480603 0.380642 0.736778 0.285117 0.371384 0.454595 0.707639 0.393273 0.269236 0.722009 0.590719 0.239305 0.503564 0.543992 0.448472 0.499368 0.224733 0.528837 0.651300 0.495616 0.529613 0.112959 0.487020 0.685246 0.551146 0.583499 0.287951 0.522351 0.260492 0.805028 0.548699 0.014042 0.535866 0.296950 0.614150 0.497483 0.001143 0.493578 0.867603 0.243911 0.294179 0.787425 0.172839 0.513368 0.175965 0.713504 0.592298 0.330351 0.072327 0.287755 0.456681 0.838695 0.103294 0.522133 0.669220 0.518509 0.543210 0.360343 0.642833 0.402295 0.682349 0.612477 0.339850 0.209222 0.587617 0.444989 0.596287 0.384901 0.587617 0.444989 0.596287 0.384901
Loading
Loading