Skip to content

T23697: Add GridAnimation interface and grid animations#8

Closed
smspillaz wants to merge 19 commits intomasterfrom
T23697
Closed

T23697: Add GridAnimation interface and grid animations#8
smspillaz wants to merge 19 commits intomasterfrom
T23697

Conversation

@smspillaz
Copy link
Copy Markdown
Contributor

Adds a GridAnimation template and helper functions, used to animation surfaces by deforming equally spaced vertices on a texture.

The MagicLampAnimation reimplements the "magic lamp" effect from Compiz.

https://phabricator.endlessm.com/T23697

@smspillaz
Copy link
Copy Markdown
Contributor Author

Depends on #4

@smspillaz smspillaz closed this Aug 31, 2018
@smspillaz smspillaz reopened this Aug 31, 2018
@smspillaz smspillaz force-pushed the T23697 branch 5 times, most recently from bbb7dd7 to 4146937 Compare September 9, 2018 20:18
Sam Spilsbury added 19 commits September 12, 2018 14:59
If we know that two points are going to be of the same type,
it is convenient to be able to compare them with the == operator
as opposed to having to use agd::equals
The "box" type represents a simple perpendicular quadrilateral
in 2D space given by its top left and bottom right co-ordinates
(eg, x1, y1, x2, y2).
This is necessary in order to use it with some of the testing
operators (like agd::Equals or agd::AlmostEq), since those
testing operators use operator<< in order to print out the
contents of the type in the event of a matching failure.
We're going to do affine transformations and glm is a particularly
well respected library for things like that. Note that the dependency
is a submodule, so you'll need to fetch and pull submodules before
building.
Steppers encapsulate some progress change within the animation. The
typical case is linear stepping, which just interpolates from
0 to 1. However, another case is reverse stepping which is
composed by 1.0f - linear (ms).

Future steppers can be added to add all sorts of easing parameters.

https://phabricator.endlessm.com/T23543
Right now we only have 2D vector types, but some consumers like
clutter want bounding volumes in a four dimensional space, so we
need a type to represent such bounding volumes.
We'll do some affine transformations based on this one.

There's three main methods to override here:

 - Step() -> Take a single step in the animation by a given
             number of milliseconds. Returns true if more
             steps need to be taken, false otherwise
 - Matrix() -> Get the transformation matrix for the animated
               surface at this point in the animation.
 - Extremes() -> Get the four points specifying the 3D bounding
                 volume for this surface, usually transforming
                 the co-ordinates of the surface in scene-relative
                 co-ordinates that have been passed in.
We'll use these later for the affine transformation animations
to calculate box offsets.

Usually we want to compute where the center of a box is in both
absolute co-ordinates and relative co-ordinates. ComputeBoxCenter
will do it in absolute co-ordinates and ComputeBoxCenterOffset
in co-ordinates relative to the top left corner of the box.
We'll use these later when implementing linear progress based
animations.

Right now we only have clamp(), used to ensure that the given value
sits between some bounds.
These are just some simple macros to define ABI safe property
get/set functions. They are accessed by calling the correct overload,
for instance, to set properties:

    animation.Target(target).Source(source)

And to get properties:

    auto const &target = animation.Target();
These helpers are used to quickly append a new "interface"
construction prop to the existing construction properties
and make it easy to lookup the props inline.

Basically, we need these helpers because we are wrapping some
C++ interface implementation in a GObject. GObject doesn't require
all the properties to be set at construction time, whereas
constructing the C++ interface implementation does. So we need to
collect all the properties in the constructor and then construct
the underlying C++ interface there.

Doing this by looking over all the construct properties and assigning
values would be cumbersome, especially doing it for every effect
implementation. Instead, we put all the construct properties into
a hash table (hashed by the property name) and then fetch them in
O(1) using the InterfaceConstructor template.

This template constructs the "Interface" type (the type passed
should be the implementation of the interface you want to construct)
and variadically takes in all the construction parameters. Use
ForwardValueFromHT to extract a typed value from the given property name
from its corresponding GValue in the construct properties hash table. For
instance, supposing that MyType's constructor is defined as follows:

MyType::MyType (float                   bar,
                animation::Point const &baz)
{
  ...
}

You would use the following in the constructor override for the GObject
wrapper:

const char *interesting_props = {
  "bar",
  "baz",
  NULL
};
g_autoptr(GHashTable) props_ht =
  static_hash_table_of_values_for_specs (interesting_props,
                                         construct_params,
                                         n_construct_params);
auto *interface = InterfaceConstructor <MyType>::construct (
  ForwardValueFromHT (props_ht, g_value_get_float, "bar"),
  ForwardValueFromHT (props_ht, animation_point_from_gvalue, "baz")
);
g_auto(GValue) interface_value = G_VALUE_INIT;
unsigned int new_n_construct_params = 0;
GObjectConstructParam *new_construct_params =
  append_interface_prop_to_construct_params (construct_params,
                                             n_construct_params,
                                             object_class,
                                             &interface_value,
                                             interface,
                                             &new_n_construct_params);

object_class->constructor (type,
                           new_construct_params,
                           new_n_construct_params);
This will be useful for printing out the contents of
matrices when tests fail. Also necessary if we want to use
comparison matchers with them.
This matcher allows the verify that one type instance is
"almost equal" to another type instance. The way that is
done is that the type must implement the
animation::matcher::AlmostEqTrait type trait and define
the apply() method.
This is a simple affine transformation that goes from
one box to another.

The animation runs "from" some source point "to" some target point
which is the surface' "natural" position within the scene (eg, if
there were a scene graph, the "to" position forms its co-ordinates
and its geometry).

For instance, if the window was minimizing, then the animation would
have to be run in reverse, "from" the minimized point "to" the unminimized
point, but stepping backwards.
This animation simulates a gentle bounce on a sine wave from
the center of the window outwards.

The best way to think of this animation is to think of an attenuating
sine wave squeezed inwards by two bounds that converge on a single point.

In thise case those lines are running from the "initialScale" to 1.0
and the "maximumScale" down to 1.0. We run the sine wave for 2pi * nBounce
iterations (scaling it it to fit within the time range), but the effect
of the naimation is scaled according to where we are on the bunds.

Now, rotate the attenuating sine wave so that it is facing towards you
on the z-axis (OpenGL co-ordinates). This is essentially what the animation
is.
This animation rotates the surface from some rotated position
towards a resting position where its rotation angle is
0, 0, 0 on all three axes. The axis that the surface can rotate
on is configurable in unit-cordinates. Rotation on 0.5, 0.5 rotates
at the center of the surface, whereas rotating from 0, 0 would rotate
from the top left corner.
The GridAnimation interface represents transformations that are done
on a grid of vertices  that a surface is subdivided into. The
transformation can be any arbitrary function. Implementations must
override three functions:

 - Step() -> Take a single step on the function according to the
             number of milliseconds passed. If more steps are needed
             to complete the animation, return true, otherwise return
             false.
 - DeformUVToModelSpace() - Given some co-ordinates between (0, 0) and
                            (1, 1), deform the co-ordinates into model
                            space, that is the space in the scene of
                            the surface as if the function were applied.
 - Extremes() - Get the geometry of a 2D bounding plane totally encompassing
                the deformed surface.
This animation simulates the "genie" effect on another popular
operating system, appearing to "suck" the window into a destination
rectangle.

The way it works is a little complicated. Basically, we subdivide
the surface into a very high resolution grid and from each point
in the source geometry to each point in the target geometry, we
we have a sigmoid function (S-curve, eg e^x / (1 + ke^px) that each
grid point travels down towards its destination.

When deforming a UV into the pre-computed grid we must do a linear
interpolation along both axes, finding the nearest subdivision source
point on a UV mesh, then interpolating into that mesh component
on the scene-relative internal mesh.

https://phabricator.endlessm.com/T23697
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.

1 participant