MASARYK U N I V E R S I T Y FACULTY OF INFORMATICS Animation module for Age Master's Thesis LÁZÁR B E N C E KIS Brno, Spring 2025 MASARYK U N I V E R S I T Y FACULTY OF INFORMATICS Animation module for Age Master's Thesis L A Z A R B E N C E KIS Advisor: Mgr. MarekTrtik, Ph.D. Department of Visual Computing Brno, Spring 2025 Declaration Hereby I declare that this paper is m y original authorial work, which I have worked out o n m y o w n . A l l sources, references, a n d literature used or excerpted during elaboration of this work are properly cited and listed i n complete reference to the due source. During the preparation of this thesis, I made use of Al-based tools to support both the development a n d writing processes. These tools were employed i n accordance w i t h the principles of academic integrity. I carefully reviewed and validated all suggestions provided, and I take full responsibility for the content a n d accuracy of this thesis. Lázár Bence K i s Advisor: M g r . Marek Trtik, P h . D . iii Acknowledgements I want to thank m y advisor, Mgr. Marek Trtik, Ph.D. for his invaluable assistance during the preparation of this thesis. I a m also grateful for the support of m y girlfriend a n d family, w h o helped me to not lose m y sanity. I also want to thank Ashborne Games for providing me w i t h ideal conditions for finishing m y studies. SDG. iv Abstract Animation systems are an integral part of modern game engines. They provide an interface for interpolation of numerical values, skeletal animation, and smooth blending of animation clips. This thesis presents the design and implementation of a comprehensive animation module for Age, the academic game engine developed by the Department of Visual C o m p u t i n g at FI M U , and includes an overview of commonly used animation solutions i n m o d e r n game engines. The developed features are demonstrated through benchmark and tutorial applica- tions. Keywords animation, interpolation, game engine, game development, C + + v Contents Introduction 1 1 Theoretical background 3 1.1 Intuition 3 1.2 Curves 3 1.2.1 Curve representations 4 1.2.2 Polynomial curves 5 1.2.3 Piecewise functions and splines 7 1.3 Interpolation 8 1.3.1 Interpolation methods 8 1.4 Animation concepts 13 1.4.1 Keyframes 14 1.4.2 Animation track 14 1.4.3 Animation clip 15 1.4.4 Blending 15 1.4.5 Partial blending 16 1.4.6 Additive blending 17 1.4.7 Blend spaces 18 1.5 Skeletal animations 19 1.5.1 Linear blend skinning 21 2 Overview of existing solutions 24 2.1 General Animation Properties Across Engines 24 2.1.1 Interpolation Methods 24 2.1.2 Skeletal Animation a n d Rigging 25 2.1.3 Inverse Kinematics 26 2.1.4 Animation Retargeting 26 2.1.5 Event Systems 26 2.1.6 Blend Trees and Finite State Machines 26 2.1.7 Root M o t i o n 27 2.2 Unity 27 2.3 Unreal Engine 27 2.4 Godot 28 2.5 Blender 29 2.6 Comparison summary 30 vi 3 Age 33 3.1 The com M o d u l e 33 3.2 The gfx M o d u l e 36 3.3 Other Modules 37 4 Implementation 39 4.1 Design Considerations 39 4.2 M i n i m a l Example 39 4.3 Building Blocks 40 4.3.1 Keyframe 40 4.3.2 Interpolation 42 4.3.3 A n i m a t i o n Track 44 4.3.4 A n i m a t i o n 45 4.3.5 AnimationPlayer 45 4.3.6 Animator 46 4.3.7 Scalar 46 4.4 Skinning and Skeletal A n i m a t i o n 46 4.4.1 Simple curve editor 50 5 Evaluation 52 5.1 Evaluation of the Interpolation Algorithms 52 5.2 Evaluation of Skeletal Animations 53 6 Conclusion 54 6.1 Future 54 A A n appendix 55 Bibliography 57 vii List of Tables 2.1 A comparison of animation functionalities across Unity, Unreal, Godot a n d Blender 32 4.1 A comparison of .9513.6.... and .9513.6 for storing keyframe values 41 5.1 Result after measuring .9513.6...- 52 5.2 Result after measuring .9513.6 53 viii List of Figures 1.1 Visualization of a simple wave movement 4 1.2 y = x3 w i t h an inflection point at x = 0 6 1.3 Runge's phenomenon: the blue function is a fifth order, whereas the green is a ninth order polynomial trying to match the red function y = 1 + 2 5 x 2 [1] 7 1.4 Interpolation functions captured i n Age from left to right: constant, linear, Bezier; and i n the bottom row variants of the eased function w i t h parameters of 4, —10 and —0.5 respectively 9 1.5 BlendSpace2D i n Godot. Each point represents an animation. Between points the shown triangulation is applied, and the surrounding three animations are blended together. 18 1.6 Skeletal structure 19 1.7 The candy wrapper effect 23 2.1 Interpolation methods and their impact i n Blender . . . . 25 2.2 Supposedly linearly interpolated keyframe 28 4.1 A rigged bar w i t h the vertices m a p p e d to the root bone highlighted i n orange 48 ix Introduction Animation is the process of creating the illusion of motion and change by displaying a sequence of static images or frames in rapid succession. The term originated in the film industry, where it traditionally involved drawing or capturing individual frames that each depicted a slight change, w h i c h were then played sequentially to convey movement. H u m a n s are accustomed to perceiving motion, and i n interactive digital media, animators simulate physical principles to maintain realism, coordinating time-based changes w i t h i n a virtual scene. W i t h advancements i n digital tools, animators define only key states or poses, with intermediate values calculated through mathematical techniques like interpolation. This computational approach streamlines the workflow and enables dynamic, real-time animation. In video games, animation not only serves as feedback to player actions but also supports narrative storytelling and gameplay mechanics. Beyond functionality, it enhances aesthetic appeal, contributing to a more immersive experience. The Academic Game Engine (Age) is developed at the Department of Visual C o m p u t i n g at the Faculty of Informatics at M a s a r y k University for educational purposes. It is designed to be modular and planned to be put into operation i n the game development courses, where the students could get hands-on experience w i t h low-level engine programming. Problem Statement The Age game engine currently lacks a framework for creating animation. This thesis addresses this limitation by designing and implementing a generalized, extendable, and user-friendly animation module for Age. The proposed system facilitates interpolation between numerical values, vectors, and quaternions, forming a basis for skeletal animation and skinning techniques. Additionally, it leverages Age's reflection system to animate any numerical property, allowing for versatile and reusable animation components. Blending between animation clips is also supported to enhance fluidity and realism. 1 INTRODUCTION To demonstrate its capabilities, practical examples w i l l illustrate key functionalities, and performance benchmarks w i l l assess computational efficiency and scalability. Thesis Outline Chapter 1 presents the theoretical framework, covering the essential mathematical constructs and tools used i n animation systems. Chapter 2 analyzes existing animation solutions, highlighting their strengths and limitations. The next chapter introduces the Age game engine, detailing its architecture and existing modules. Chapter 4 explains the integration of the animation module into Age, while Chapter 5 provides benchmark results to evaluate the system's performance. The final chapter concludes the thesis by summarizing findings and proposing directions for future work. 2 1 Theoretical background This chapter provides a comprehensive overview of fundamental concepts in animation systems, focusing on interpolation, animation concepts, and skeletal animation techniques. It begins by showing an example to facilitate an intuitive understanding of the basic components required for animation. Afterwards, it continues by examining curves and splines and explores various interpolation methods. Subsequently, key animation concepts, like keyframes, animation tracks, and animation clips, are formalized, followed by a discussion on blending. The chapter concludes w i t h an analysis of skeletal animation methods. 1.1 Intuition W h e n discussing animation, we typically refer to the smooth motion that transitions between two defined poses. These poses are k n o w n as keyframes, and the continuous motion connecting them constitutes the animation itself. Consider, for example, a simple hand-raising gesture used in a wave. This motion consists of two keyframes: the initial pose w i t h the a r m lowered, and the final pose w i t h the a r m raised. The movement that connects these poses is called interpolation, and the trajectory followed by the h a n d forms a curve i n space. We call the point i n time associated w i t h the keyframe a timestamp. Inspired by anatomical models from the real world, it is also common in computer animation and simulation to describe motion through the changes occurring in hierarchical structures composed of bones and joints. The previous example of a hand being raised can be described simply by tracking the rotation of the shoulder and the elbow joint, and it is obvious that the surrounding skin moves w i t h them. 1.2 Curves A curve can intuitively be thought of as the path traced out by a moving point. More formally, it is defined as the image of an interval mapped to a topological space by a continuous function. A s w i l l be discussed in this section, interpolation is a specific form of curve-fitting, allowing 3 l . THEORETICAL BACKGROUND Figure 1.1: Visualization of a simple wave movement curves to be interpreted as the result of interpolation, too. In the context of animation, a curve generally represents the progression of a certain property over time, represented using mathematical functions. 1.2.1 Curve representations Curves can be represented i n various forms, each w i t h its advantages and disadvantages. The optimal expression depends on the specific task. The most c o m m o n representations1 are the explicit, the implicit, and the parametric form. Explicit form In this representation, curves are defined as mathematical functions of the form y — f(x). W h i l e straightforward, explicit functions cannot represent multiple values for the same x, m a k i n g them unsuitable for vertical lines or closed shapes like circles. Implicit form Implicit curves are expressed as equations of the form F(x,y) = 0. For example, a unit circle centered at the origin could be defined as x2 + y2 — 1 = 0. Despite their ability to represent multi-valued functions, their primary drawback is the absence of a natural traversal parameter, w h i c h can make point 1. Other representations, such as polar or vector form, also exist but are less commonly used and will not be discussed here. 4 i . THEORETICAL BACKGROUND sampling computationally intensive, especially for nonlinear equations. Parametric form Parametric curves define each coordinate as a function of a shared parameter. For example, the same circle can be expressed as P(t) = (f(t),g(t)), where f(t) = R-cos(t), g(t) = R • sin(t) and 0 < t < In. In computer graphics, the parametric f o r m is the most w i d e l y used, preferred for its flexibility and intuitive control. It can represent multivalued curves and facilitates geometric transformations, as each coordinate is managed independently. 1.2.2 Polynomial curves While any function can theoretically define a parametric curve, polynomials are most commonly used i n practice due to their simplicity and desirable mathematical properties. A polynomial function of degree n can be expressed as: • ao,...,an are the coefficients of the polynomial, • an 0, and • x is the independent variable. Polynomial curves are inherently smooth and infinitely differentiable, making them suitable for creating smooth transitions i n animation. The degree of a polynomial is determined by the highest exponent of the independent variable w i t h a nonzero coefficient. Polynomial curves can be classified as follows: Constant (degree 0): Produces a flat line, the independent variable has no impact. Linear (1st degree): Produces straight lines, useful for simple linear interpolation but limited i n versatility. where: 5 i . THEORETICAL BACKGROUND Figure 1.2: y = x3 w i t h a n inflection point at x = 0 Quadratic (2nd degree): Forms parabolic shapes, offering slightly more flexibility than linear curves. However, they still lack sufficient control for more complex shapes. Cubic (3rd degree): Provides sufficient flexibility to model complex shapes while maintaining numerical stability. They are the lowest degree polynomials that can form inflection points, w h i c h means that their curvature can change its sign, m a k i n g them particularly versatile for curve design. Higher-degree polynomials (4th degree a n d above) can interpolate more points a n d provide more control, but they are prone to excessive oscillation, a problem k n o w n as Runge's phenomenon. This effect highlights that increasing the degree of the polynomial does not necessarily result i n more accurate approximations a n d can actually worsen the curve's behavior at the boundaries. Therefore, cubic polynomials are typically preferred as they strike a balance between control, smoothness, a n d numerical stability. 6 i . THEORETICAL BACKGROUND Figure 1.3: Runge's phenomenon: the blue function is a fifth order, whereas the green is a ninth order polynomial trying to match the red functiony = — ^ - 2 [1] 1.2.3 Piecewise functions and splines To avoid the oscillation problems associated with Runge's phenomenon, when a function needs to interpolate over more than four data points, multiple lower-degree polynomials can be combined using piecewise functions, rather than relying o n a single high-degree polynomial. Piecewise functions combine multiple functions b y splitting the domain into multiple intervals, w i t h each interval having its o w n definition. B y combining multiple polynomials, w e get a piecewise polynomial curve - also called a spline. Splines can be constant, linear, quadratic, cubic, or of even higher degrees, depending o n the degree of the highest-degree p o l y n o m i a l used as a segment function i n the piecewise definition. The degree of smoothness of a spline is important for ensuring that the resulting curve transitions smoothly between the intervals. A function is said to be of smoothness at least k if its kth derivative is continuous over the domain. This property is often denoted by saying the function is of class Ck or it has C continuity. 7 i . THEORETICAL BACKGROUND Formally, a spline of degree k defined over a set of n + 1 knots XQ < %\ < ... < xn is a function S(x) such that for each interval S(x) = Si(x) V i e { 0 , 1 , . . . , » - 1 } where each S;(x) is a polynomial of degree fc, and the spline is continuous u p to its (k — l ) t h derivative. To reiterate, spline interpolation is often preferred to polynomial interpolation because it yields similar results, even w h e n using lowdegree polynomials, while avoiding Runge's phenomenon for higher degrees. 1.3 Interpolation Interpolation is the process of estimating intermediate data points between given discrete ones. In animation systems, it plays a crucial role i n generating smooth motion and seamless transitions. This section explores the use of curves i n interpolation and examines various interpolation techniques commonly employed i n animation systems. Understanding these concepts is key to defining the constructs required for animation. 1.3.1 Interpolation methods In the case of animation systems, piecewise functions are used to interpolate between keyframes. The domain of the function is split into intervals based on the timestamps of the keyframes, and each interval has a specific interpolation method assigned as its segment function to estimate the values between the two neighboring keyframes. These segment functions are of the format: V{virVi+l,T) taking three arguments, where: • V{ is the value at the first keyframe, • Vi+\ is the value at the second keyframe, and • T e [0,1] is the normalized time parameter. 8 i . THEORETICAL BACKGROUND Figure 1.4: Interpolation functions captured i n Age from left to right: constant, linear, Bezier; a n d i n the bottom r o w variants of the eased function w i t h parameters of 4, —10 a n d —0.5 respectively Let us define the normalization function N{a, b, t) used to calculate T as: M(a,b,t) = -—b — a where: • a is the lower bound (the time of the keyframe w i t h the greatest timestamp smaller than t), • b is the upper bound (the time of the keyframe w i t h the smallest timestamp greater than t), and • t is the actual timestamp. Constant interpolation Constant interpolation is the simplest form of interpolation, basically not d o i n g any interpolation but taking the neighboring keyframe's value as is. If all keyframes have constant interpolation assigned, the result is a step function. It lacks gradual changes a n d makes jumps between discrete values. In general, there can be a threshold 9 determining the location of the jump between the two values: 9 i . THEORETICAL BACKGROUND \a if T < 6 ?const{a,b,r,e) = { - . (1.1) I o otherwise By setting 0 = 0.5 w e get nearest neighbor interpolation, w h i c h makes the jump at the half-point between the two keyframes: ^nearest («/ b, f ) = f const («/ b, T, 0.5) In animation systems, however, the definition w i t h 9 = 1 is more common since this can be used to hold the value of the first keyframe unchanged until the next keyframe is encountered: *hold'a,b,r) = Vconst(a,b,T,l) If all segments use a constant interpolation, the resulting spline is of degree 0. Linear interpolation Linear interpolation (also k n o w n as Lerp) is also a simple method. It calculates the weighted average of the values at the two endpoints. Its drawbacks are the smaller precision a n d h a r d breaks at keyframes. Piecewise linear segments w i t h connected endpoints form a spline of degree 1. F L e r p (a, b, T) = a + r(b — a) = (1 — r)a + rb (1.2) Eased interpolation Eased interpolation works o n the same basis as Lerp, but it modifies T before passing it o n to the interpolator. Feased(0/k/T) = F L e r p ( f l , E ( T ) ) Easing can be achieved b y exponentiating T, w h i c h allows for accelerating or decelerating changes, rather than a purely linear one: E ( T ) = T1 ? If q > 1, we get easing in, a n d if 0 < q < 1, then easing out is applied. More complex functions, sometimes o n a sinusoidal basis, achieving ease-in-out or ease-out-in dynamics, also exist. 10 i . THEORETICAL BACKGROUND Bezier interpolation Bezier interpolation relies on Bezier curves internally. In graphics applications, we usually utilize the cubic variant because of its advantageous properties, as discussed earlier i n Subsection 1.2.1. The cubic Bezier curve is defined by four points: • PQ: the start point, which defines the value of the curve at T = 0 • P3: the end point, where the curve ends at T = 1 • Pi, Pi'- control points shaping the curve, which do not necessarily go through them The Bezier curve provides an intuitive way to construct smooth curves. Ensuring that the point and its surrounding handles all lie on the same line guarantees the continuity of the first derivative of the composite Bezier curve, as this w o u l d result i n identical tangents both at the end of the first and the start of the following segment. The parametric definition of the curve is as follows: B(M) = ( l - u ) 3 P o + 3 ( l - u ) 2 u P i + 3 ( l - u ) u 2 P 2 + u 3 P 3 ; 0 < u < 1 Since the parameter u is not linearly related to time, we cannot simply substitute the normalized timestamp T into the curve equations. Although the expression above always yields a valid time-value pair for each value of u, the mapping between u and T is non-uniform and can vary across different curve segments. To evaluate the interpolated value at a specific time T, we must proceed differently. First, the parametric definition must be split into separate equations for the time component (typically x(u)) and the value component (typically y{u)). We then substitute the desired time T into the equation for x(u) and solve for the corresponding parameter u. Once u is determined, it is used to evaluate y{u), yielding the interpolated value at time T. 2 2. By constraining the x components of the control points Pi and P2 to lie between those of Po and P3, we ensure that the cubic equation for x(u) = r always has exactly one real root in the interval [0,1]. This guarantees that the curve behaves as a well-defined function over time, returning a single, unambiguous value for each input timestamp. 11 i . THEORETICAL BACKGROUND Hermite interpolation Similar to Bezier splines, Hermite splines are also cubic curves, and they can be converted into one another. The cubic Hermite spline, i n addition to the two endpoints, defines just two tangents instead of two extra control points. The curve is expressed as [2]: H(t) = (2t3 - 3t2 + 1)P0 + (t3 - 2t2 + t)T0+ (-2t3 + 3t2 )Pi + (t3 - t2 )Tx where: • PQ and P i are endpoints of the curve. • To and T\ are the tangent vectors at PQ and P i . To convert from a cubic Hermite spline to a cubic Bezier, the control points can be calculated by adding ^ of the tangents to the end points: Q> = Po + y ; Q = P i - ^ To calculate the Hermite tangents from the Bezier handles, we do the opposite: r 0 = 3 ( C 0 - P o ) ; Ti = 3 ( P i - C i ) Spherical linear interpolation The interpolation of orientations needs to be approached differently. Spherical linear interpolation (or Slerp for short) provides a way to calculate a constant-speed motion along an arc. It allows for linear interpolation between two orientations, but instead of interpolating between the endpoints linearly, it finds the shortest path along the surface of a sphere. For two unit vectors A and B, it can be calculated as follows [3]: S l « p ( A B , T ) = S ' " ' ( 1 J ) f l l A + ^ B sin(9) sin{9) With quaternions qo and q\, it can be simplified to this formula [3]: Slerp(<7o,<7i,T) = <7oO?oV)T 12 i . THEORETICAL BACKGROUND Normalized linear interpolation Normalized linear interpolation (or Nlerp for short) is an alternative to Slerp. It calculates the linearly interpolated variant of the quaternions, w h i c h w o u l d lie o n a line instead of o n a circular arc by itself but b y normalizing the result, it is ensured to be o n the 4 D unit sphere. A l t h o u g h it is mathematically less accurate and does not ensure a constant velocity of rotation, it can be faster than Slerp by avoiding the use of trigonometric functions w h i l e providing similar results w h e n interpolating between two orientations w i t h a small difference [4, 5]. Interpolation in higher dimensions The above interpolation functions are defined over values from R but they can be easily extended to higher dimensions b y interpolating between the components individually. This w o u l d simply mean that to interpolate between two 3 D points A, B G R 3 , we need to interpolate the x, y, a n d z components individually: However, this technique fails w h e n the components are not independent—as is the case w i t h quaternions or rotation matrices. In such cases, specialized interpolation functions are required that account for the interdependence of all components, such as the above-defined Slerp. 1.4 Animation concepts A s mentioned i n the Introduction, earlier it was necessary to prepare every frame of an animation manually. Computers, however, allow us to simplify this process by having to define just a subset of all frames and, w i t h the help of the above-discussed interpolation methods, F ( A , B , T ) V{AX,BX,T) V{Ay,By,T) F ( A Z , B Z , T ) ) 13 i . THEORETICAL BACKGROUND calculate the rest. This section describes the hierarchy of keyframes, animation tracks, and clips. 1.4.1 Keyframes A keyframe defines the value of a property at a specific point i n time. This association guarantees that during playback, w h e n the specific time is reached, the modified property equals the value. If the playback time is somewhere between two keyframes, the value is interpolated between the neighboring keyframes using the assigned interpolation method, which is parametrized by the relative distance from the times- tamps. Formally, let keyframe K{ be defined as a tuple: K{ = (ti,Vi,Fi) where: • t\ £ R: The timestamp of the keyframe. • V\ G Rr f : The value associated w i t h the keyframe, where d represents the dimensionality of the value. • Ff. The interpolation method to be used to calculate values between Ki and Kj+i.3 1.4.2 Animation track A n animation track consists of a list of keyframes and an associated parameter that is to be updated according to the keyframes. It represents the timeline of changes. Formally, let the animation track Aj be defined as a pair: where: • Kj — {K\, K2,..., Kn } is an ordered set of keyframes such that: Vz G {1,2,...,n — 1} : t{ < ti+1 3. Since a sequence always contains one more item than the number of gaps between them, the interpolation of the last keyframe is never used. 14 i . THEORETICAL BACKGROUND • Pj G V is the target parameter of a specific object w h i c h is affected by the changes such that the dimensionality of the parameter is d. Let: Aj(t) = Fi(vuVi+1,N(tuti+1,t)) represent the interpolated value of parameter / at time t, where i is calculated as the index of the keyframe w i t h the highest timestamp not greater than t. O n a sorted array of items, binary search can be utilized to find i i n O(log(n)) time. 1.4.3 Animation clip A n animation clip is a set of tracks that need to be played back simul- taneously. Formally, let the animation clip Q- be defined as the set: Ck = {A1/A2/.../Am} where each Aj is an animation track as defined above, a n d no two tracks share the same target: Vz,; e {1,2,...,m} : i ^ ; = > p,- ^ pj 1.4.4 Blending Animation blending, or cross-fading, is a technique used to combine or smoothly transition between different animation clips, giving the i m pression that one animation flows naturally into the next. Cross-fading is typically done i n a similar fashion to h o w keyframe interpolation works. Given two animation clips Ca a n d Q , and two timestamps ta and fy, the blended value Vj(ta, u) for each parameter pj is defined as: Vj(ta,tb,u) = (1 - U.) • Ca{pj,ta) + U • Cb(pj,tb) Where: • u E [0,1] is the blending factor. 15 i . THEORETICAL BACKGROUND • t) = Aj(t) represents the interpolated value of the parameter pj i n clip Q at time t. The blending factor u represents the current percentage of the transition and is calculated using our usual normalization formula M(tstart, tend't, Where: • tstart is the starting timestamp of the transition. • tend is the ending timestamp of the transition. • t is the current timestamp. To ensure a smooth transition, the blended intervals can be selected based on similarity. The timestamps at w h i c h the transition should occur could be manually associated or algorithmically calculated for pairs of animation clips. Note that blending i n this f o r m assumes that both clips target the same parameters. If they were not identical, the process w o u l d become more complex and the parameters w o u l d either need to be remapped, the missing parameters need to be ignored or interpolated w i t h default values. Frozen transition In a frozen transition, the clip we are transitioning from is paused, w i t h its values fixed. Meanwhile, the clip we are transitioning to continues to play. Over time, we interpolate between the fixed frame of clip A (where it is paused) and the continuously changing frames of clip B. Smooth transition In a smooth transition, both the clip being transitioned from and the one being transitioned to are played simultaneously. A s a result, both frames change w i t h each update, and interpolation occurs between the changing frames of both clips. 1.4.5 Partial b l e n d i n g In certain cases it can be desirable to blend the individual tracks with varying weight, allowing for more nuanced control over w h i c h prop- 16 i . THEORETICAL BACKGROUND erties are affected by each animation. For example, we might want to keep the walking animation running, while gradually blending in the wave animation targeting only the right hand. This can be achieved w i t h the help of the technique called partial blending. This extends blending w i t h weights assigned to each track determining how much impact the blending w o u l d have o n that specific track. Formally let the partially blended value be defined as: Vj(tA/tB/u,Wj) = (1 - Wj) • (1 - u) • CA(pj,tA) +Wj-u- CB(pj,tB) where W; G [0,1] is the weight determining the contribution to the value of pj from clip Ca and Cb. 1.4.6 Additive blending Additive blending is a technique used to create new animation variants by layering modifications on top of existing clips. Conceptually, if we have both a standard w a l k animation and a tired w a l k animation, we can compute the "difference" between them—effectively isolating the characteristic changes that convey tiredness. This extracted delta, representing the "essence of being tired," can then be applied to other animations. For example, applying it to a running animation w o u l d produce a tired run. A n additive animation stores the relative changes between a base animation and its modified counterpart. It is not meant to be played in isolation; instead, it is applied on top of another animation clip to modify its parameters. This layering occurs after the base animation has already been evaluated, allowing the additive animation to adjust the pose or motion without completely overriding the underlying clip. Formally, let Ca^b be an additive clip, such that: V/G { l , 2 , . . . , m } , £ G [0,1] : Ca^b(pjrt) = Cb(pjft) 9 Ca(pj,t) The blending of the clip Q and the additive clip Ca^b w o u l d then be: V; G { 1 , 2 , . . . ,m},t G [0,1] : Cc+a^b(pj,t) = Cc(pjft) ®Ca^b{pjrt) The symbols © and 0 denote special operations in this context. Since blending is typically performed between animations targeting the 17 i . THEORETICAL BACKGROUND -s * • A K x:0.1 0 y: 01 0 Sync: • Blend: Continuous v Point 12 0 0.5 0 1 Output • ebuqqer(S) Audio Animation AnimationTree Shader Editor Figure 1.5: BlendSpace2D i n Godot. Each point represents an animation. Between points the s h o w n triangulation is applied, and the surrounding three animations are blended together. same humanoid character, these operations often correspond to spatial transformations and their inverses. Mathematically, there is nothing preventing us from defining additive blending over simple real n u m bers. However, i n practice, additive clips most commonly operate on transformation data—either as full transformation matrices or as septuples consisting of three positional coordinates and four quaternion components representing rotation. 1.4.7 Blend spaces The blending described i n Subsection 1.4.4 can be understood as a 1-dimensional blend space, since it operates based on a single parameter u that varies over time. However, blend spaces can be extended to multiple dimensions, allowing for smooth interpolation between animations based on two or more input parameters. A c o m m o n application of 2 D blend spaces is i n character locomotion systems. In most games, a character can move along two axes—typically forward/backward and left/right—which correspond 18 i . THEORETICAL BACKGROUND Figure 1.6: Skeletal structure to two input variables representing movement velocity i n those directions. Instead of manually selecting and blending animations for each possible movement vector, a 2 D blend space distributes animations (e.g., w a l k i n g forward, strafing left, w a l k i n g backward, etc.) across a two-dimensional plane. Intermediate poses are computed using barycentric interpolation over the triangle within the plane that contains the current velocity vector. This provides a seamless a n d responsive transition between animations, supporting a more natural a n d fluid character movement. 1.5 Skeletal animations Skeletal animation is a technique for deforming characters over time. Meshes consist of a visible layer, called skin i n this field, and an invisible bone hierarchy - the skeleton. They are expected to be modelled i n their most natural pose, which then serves as a basis for deformations. This configuration is called the bind pose, also k n o w n as rest pose, because 19 i . THEORETICAL BACKGROUND the skin is b o u n d to the underlying skeleton i n this initial pose. To defined the relation between the skin and the skeleton, vertices of the skin are assigned to bones w i t h specific weights. These weights determine h o w m u c h influence the given bone has on the deformed position of a specific vertex. Bones w i t h the highest assigned weights impact the vertex the most, while bones w i t h zero weight have no effect on the final position, even if they move. To simplify the process of weight assignment, digital content creator tools and modelling software usually provide a graphical interface. U s i n g this the artist can "paint" the weights w i t h a b r u s h onto the surface of the mesh. The software most often also provide a way to automatically normalize the weights, so that the sum of weights for each vertex would always be zero. In theory, any number of bones can be assigned to a vertex, but most of the time no more than four bones are needed to achieve satisfying results. To reduce the chance of artifacts, the b i n d pose is usually i n T pose or A pose, w h i c h refer to the shape of the letter the arms form. This is required because due to the high flexibility of the arm a lot of deformation occurs i n the area of the shoulder and the armpit. A s such, we need to consider what movement is expected and choose the rest pose that accommodates that the best. In the case of non-humanoid characters the shape of the b i n d pose can vary widely, but the goal is the same: to achieve a natural pose w i t h least possible amount of deformations baked in. Advantages The advantages of this method are the simpler interface for animators compared to vertex animations, since they can move related parts of an object simultaneously without disrupting the structure. It is simple for us to think of living creatures i n terms of their skeletal structure, and we are used to the movement of limbs and other rules of body part physics from the real world. It also makes it possible to reuse the same animations if the same skeleton is shared between multiple objects. It is advantageous even with regard to performance and memory footprint, since the keyframes need to be stored and the transformations need to be calculated per bone, not per vertex. It allows to create dynamic animations, since the bones can be easily manipulated during runtime 20 i . THEORETICAL BACKGROUND and create real-time procedural effects. It also serves as a good basis for blending multiple animation clips by interpolating the individual bone transforms. Inverse kinematics (IK) also relies o n the existence of a skeleton of bones and joints. Disadvantages When it comes to drawbacks, skeletal animations require a rather complex setup, the process of assigning weights to vertices can be tedious and error-prone. During the process of skinning various artifacts can appear, such as the candy wrapper effect i n the case of linear blend skinning, w h i c h can be mitigated only by resorting to more complex procedures, such as dual quaternion skinning. A bigger emphasis needs to be placed o n optimization as well, to make sure that the work is well-divided between the C P U and the G P U without extreme bottlenecks. 1.5.1 Linear blend skinning Linear Blend Skinning (LBS), also known as Skeleton Subspace Deformation (SSD), is a widely used algorithm for mesh deformation based on skeletal animation. In LBS, the position of each deformed vertex is computed as a weighted sum of the vertex's positions transformed by the matrices of the influencing bones. Each bone's transformation matrix is obtained by multiplying the inverse of its bind pose matrix w i t h its current transformation matrix. This process first transforms the vertex into the local coordinate space of the bone using the inverse b i n d pose matrix, and then moves it to its final position using the bone's current transformation. Let: • v be the homogeneous coordinates of the vertex i n bind position • v' be the homogeneous coordinates of the deformed position of the vertex • Wj be the weight of bone i (with yj Wj usually required to be 1 to avoid unexpected changes) 21 i . THEORETICAL BACKGROUND • T,- be the transformation matrix of bone i Then: v' = Y^Wi • (T,- • v) During every update, the transformation matrix T,- needs to be recalculated for all of the bones as follows: Tf = Tcurrenti ' ^bindi Where: • ^current; stands for the current w o r l d space transformation of bone i • Tbindi i s the b i n d pose transformation matrix of bone i Tbindi is fixed for the lifetime of the object, but T c u r r e n t i can change every frame and needs to be updated. G i v e n a hierarchical bone structure in w h i c h each bone, except the root, has exactly one parent, and the transformation from the parent's coordinate space to the bone's local space is known, the world transformation of any bone can be computed recursively. This is achieved by successively multiplying the relative transformations along the bone chain, starting from the given bone and continuing up to the root. Let: I if i is the root bone's index TcurrentP{f) • R; otherwise • I stands for the identity matrix • P(i) is the index of z's parent bone • R; is the transformation matrix to the parent's space f r o m the child's local space ^currenti — Where: 22 i . THEORETICAL BACKGROUND Figure 1.7: The candy wrapper effect Despite its simplicity and efficiency, Linear Blend Skinning can produce noticeable artifacts due to its non-volume-preserving nature. C o m m o n issues include joint collapse and the "candy-wrapper" effect, particularly during bone rotations. These artifacts arise because L B S performs component-wise linear interpolation of transformation matrices, which, even when interpolating between rigid transformations, can yield non-rigid results. 23 2 Overview of existing solutions In this chapter, an overview and comparative analysis of animation systems i n prominent game engines is presented, focusing on Unity, Unreal Engine, Godot, and Blender. Unity and Unreal Engine dominate the market [6], followed by the open-source project Godot. Blender, though not a game engine, is included due to its widespread use i n the game industry for asset creation and animation authoring. 2.1 General Animation Properties Across Engines A t a conceptual level, game engines share foundational animation principles, including interpolation methods, rigging structures, and the use of state machines. However, the implementation and feature sets vary significantly across engines. Let us first look at these common properties, and then list the unique features of each of them. 2.1.1 Interpolation Methods A l l contemporary game engines implement some form of keyframe interpolation, enabling smooth transitions between animation states. Linear interpolation is universally supported across the examined engines, providing direct, unmodulated transitions between keyframes. More advanced interpolation methods vary in availability and imple- mentation. Unity and Unreal Engine support cubic interpolation via Hermite splines, enabling smoother transitions through user-adjustable tangents. Both engines are also capable of automatically computing or even clamping tangents to maintain curve continuity and ensure visually coherent interpolation across keyframes. Blender, by contrast, utilizes Bezier curves, granting animators full control over both the curve shape and its tangents. Additionally, it offers optional easing and bounce presets of various strengths, which serve as shortcuts for applying common temporal adjustments without the need for manual curve editing. Godot occupies a m i d d l e g r o u n d i n terms of interpolation flexibility. It allows users to choose between simplified modes—constant, 24 2. OVERVIEW OF EXISTING SOLUTIONS Easing (by strength) Dynamic Effects T J/ Sinusoidal T \ / Quadratic £ / Cubic V Quartic \J Qumtic _J Exponential _J Circular Back Bounce \/\~ Elastic T T T T Figure 2.1: Interpolation methods a n d their impact i n Blender linear, or cubic interpolation—without exposing tangent handles d i rectly. Additionally, it introduces the option to apply easing factors to linear interpolations, enabling smoother transitions even i n otherwise unmodulated keyframe sequences. However, it also offers a Bezier interpolation mode for users requiring greater control. 2.1.2 Skeletal Animation and Rigging A l l major game engines support skeletal animation, w h i c h enables bone-based manipulation of character meshes through hierarchical joint structures. Unlike Unity and Godot, which rely o n external Digital Content Creation ( D C C ) tools for r i g construction a n d skin weight painting, Unreal Engine distinguishes itself b y offering integrated rigging tools within the editor, streamlining the rigging and animation workflow. 25 2. OVERVIEW OF EXISTING SOLUTIONS 2.1.3 Inverse Kinematics In contrast to Forward Kinematics (FK)—where the animator explicitly defines joint rotations and the resulting pose is computed accordingly—Inverse Kinematics (IK) systems operate by allowing the user to specify a desired end position, and the system calculates the necessary joint transformations to achieve that position. IK is particularly valuable for producing context-sensitive and physically plausible limb movements, such as a character reaching for an object or foot placement on uneven terrain. The level of I K support varies considerably across engines. Unreal Engine offers some of the most advanced and integrated I K solutions, including full-body solvers, but U n i t y also provides a robust IK system. 2.1.4 Animation Retargeting A n i m a t i o n retargeting allows motion data authored for a specific skeletal rig to be adapted for use w i t h a different rig, typically within the constraints of humanoid characters. This functionality is essential for asset reuse and cross-character animation consistency i n largescale productions. Both Unreal Engine and Unity support retargeting through an intermediate representation, whereby a character's rig is mapped to a standardized internal skeleton. This abstraction facilitates the transfer of animations between differently structured rigs while preserving motion fidelity. 2.1.5 Event Systems A l l three engines incorporate event-driven systems that enable the invocation of game logic at predefined points w i t h i n an animation sequence. These events, often b o u n d to specific keyframes, facilitate synchronization between animation and gameplay, such as sound playback, particle effects, or triggering scripted interactions. 2.1.6 Blend Trees and Finite State Machines Blend trees and blend spaces provide a mechanism for dynamically blending multiple animations based on input parameters. This can be 26 2. OVERVIEW OF EXISTING SOLUTIONS especially useful i n character controllers, allowing for smooth transitions between movement states. Complementing this, finite state machines are used to structure animation logic through discrete states and transitions, governed by conditions or triggers that reflect gameplay context. A l l three engines provide a graphical user interface for visual programming of the state machine logic. 2.1.7 Root Motion Root motion refers to the extraction of translational data from the root bone of an animated character, allowing in-engine movement to be driven directly by animation rather than externally applied transformation logic. This approach is particularly beneficial for preserving physical believability i n character locomotion and avoiding the problem of feet slipping. 2.2 Unity Unity provides two animation systems: the legacy system, maintained primarily for backward compatibility, and the more advanced Mecanim system, w h i c h is the recommended standard due to its greater flexibility and functionality. W h i l e the legacy system may offer simpler setup and potentially better performance for basic animations without transitions, Mecanim excels i n handling complex animation blending and state-driven behaviors. U p o n importing animated rigs, Unity distinguishes between Generic and H u m a n o i d animation types. The H u m a n o i d rig type provides a standardized internal skeleton abstraction, enabling features such as inverse kinematics and animation retargeting through an internal Avatar system. This abstraction, while beneficial for reusability and advanced features, incurs some performance overhead, and should only be used if one these functionalities is required [7]. 2.3 Unreal Engine A s mentioned above, Unreal is actively developing a comprehensive in-engine animation authoring environment aimed at reducing re- 27 2. OVERVIEW OF EXISTING SOLUTIONS Figure 2.2: Supposedly linearly interpolated keyframe liance on external D C C tools. This includes full skeleton editing, bone addition or removal, and weight painting directly within the engine, which is rare among mainstream engines. These features might arouse interest among professional filmmakers, positioning Unreal as a viable tool for film production and real-time cinematics [8]. Unreal Engine employs Blueprints even within its animation system—a visual scripting framework specifically designed to handle complex animation logic and control. This node-based interface enables the integration of runtime logic that extends beyond the capabilities of traditional animation state machines. However, this increased flexibility also introduces additional complexity into the animation workflow. The Sequencer tool i n Unreal Engine offers granular control over animation curves, including the somewhat unconventional ability to assign interpolation modes on a per-keyframe basis rather than per segment. This design choice can result i n counterintuitive outcomes—as illustrated i n Figure 4.1—such as a keyframe w i t h linear interpolation applied producing linear behavior only w i t h i n an i n finitesimally narrow region, due to surrounding keyframes being set to different interpolation modes. 2.4 Godot A notable limitation of Godot's animation system is the absence of automatic keyframing in 3D mode. Unlike other engines that support autokeying to streamline the animation workflow, i n Godot, every 28 2. OVERVIEW OF EXISTING SOLUTIONS keyframe must be created manually, w h i c h can significantly increase production time for complex animations. A n interesting and distinctive feature is the capture mode within Godot's AnimationPlayer. This functionality enables the system to record the current runtime value of a property at the moment an animation begins, and then interpolate smoothly toward the first keyframe. This mechanism prevents sudden discontinuities or "snapping" when the initial state does not align w i t h the animation's starting pose, contributing to more seamless transitions during playback. Due to the lack of separation between animation editing and scene manipulation i n Godot, a dedicated Reset animation mechanism is employed. This special animation consists of a single keyframe for each animated property, storing its initial state. It serves as a default pose used to revert any changes introduced d u r i n g animation previews directly within the scene view [9]. 2.5 Blender Unlike other engines that may use multiple systems for keyframe animation-such as state machines, visual animation graphs, or blend trees-, Blender relies entirely on F-Curves for animating virtually all properties. This unified approach provides animators w i t h consistent and fine-grained control over timing and interpolation. The level of granular manipulation of curve tangents and the number of easing presets with stylized effects provided is exceptionally detailed in Blender, not matched by the three game engines. However, due to the scalar nature of F-Curves-each representing a single floating point number-Blender does not utilize Slerp for quaternion animation, which is the standard method for achieving constantspeed rotational interpolation. Instead, Blender performs componentwise interpolation on the four elements of the quaternion, followed by normalization to ensure the validity of the resulting quaternion. This method can be conceptually understood as normalized linear interpolation ( n L E R P ) . W h i l e computationally less intensive than Slerp, n L E R P does not guarantee constant angular velocity, and simple manipulations—such as adjusting a single quaternion component—can easily produce non-uniform rotational motion. 29 2. OVERVIEW OF EXISTING SOLUTIONS Another distinctive aspect of Blender's animation system is its treatment of rotation data. O p p o s e d to the usual practice between game engines, Blender stores different rotational representations—Euler angles, axis-angle, and quaternions—independently. This allows an object to maintain multiple rotation tracks simultaneously, each i n a different format. However, only the representation currently active i n the object's transformation settings is used d u r i n g evaluation. This design provides flexibility for animators, but requires careful management to avoid conflicts or unintended overrides between rotation modes. 2.6 Comparison summary The table below provides an overview of the functionalities supported by each program. Feature Unity Unreal Engine Godot Blender Keyframe Animation System Mecanim / Animation component Animation Blueprints / Sequencer AnimationPlayer F-Curves Curve Editor Access Yes (Animation W i n d o w ) Yes (Sequencer Curve Editor) Partial (only Bezier tracks are editable) Yes (Graph Editor, full tangent control) Autokeying Support (3D) Yes Yes N o Yes Interpolation Types Constant, Linear, Cubic (Hermite) Constant, Linear, Cubic (Hermite) Constant, Linear, Cubic, Bezier (partial) Constant, Linear w i t h easing, Cubic + Bezier 30 2. OVERVIEW OF EXISTING SOLUTIONS Quaternion Interpolation S L E R P (Mecanim Humanoids) S L E R P / n L E R P Cubic (no SLERP) Componentwise (nLERP) Separate Rotation M o d e s Euler / Quaternion (mutually exclusive) Euler / Quaternion Euler / Quaternion Euler / Axis-Angle / Quaternion (stored separately) Event Trigger System Animation Events (keyframes) Notifies / Blueprint Event Tracks Call M e t h o d Tracks / Signals Function drivers via F-Curves / markers State M a c h i n e Support Animator Controller (Mecanim) Animation Blueprint State Machines AnimationTree N o native F S M , scripting only B l e n d Tree / Space Animator Blend Trees Blend Space (1D/2D) Blend nodes i n AnimationTree N o native support; manual blend- ing/scripting Inverse Kinematics Yes (Avatar IK, plugins) Yes (Full Body IK, Control Rig) N o native support; scripting workarounds Yes (Pose Library + Constraints) Animation Retargeting Yes ( H u m a n o i d Avatar) Yes (Retarget Manager / IK Rig) ' N o native support Yes (manual or via add-ons) Runtime Skeleton Editing N o ; external D C C only Yes (bone edit + weight paint) N o Yes (full armature and weight tools) 31 2. OVERVIEW OF EXISTING SOLUTIONS Root M o t i o n Support Yes Yes Partial / scripted Yes Partial A n i m a t i o n Playback Yes (clip ranges) Yes (Sequencer sections) Yes (markers i n Animation- Player) Yes (timeline scrubbing, N L A strips) Visual Scripting for Animation Logic N o ; C# or Animator transitions only Yes (Blueprint) Yes (Visual scripting + signals) N o ; Python scripting only Table 2.1: A comparison of animation functionalities across Unity, Unreal, Godot and Blender 32 3 Age Age stands for Academic Game Engine. Age is developed at the Faculty of Informatics at Masaryk University. It w i l l provide a playground for students of the game development programme and w i l l assist them i n gaining a hands-on experience w i t h low-level engine programming. One of the core concepts of A g e is modularity. This means that functionalities are expected to be bundled into modules, for the ease of replacing them entirely. This c o u l d allow the students to implement their o w n version of one of the modules d u r i n g their studies. The following sections describe the modules existing at the time of writing. The modularity is further emphasized w i t h the help of L i braries. These provide interfaces accessible from other parts of the Context to facilitate interoperability between the various modules. The libraries function as singletons, and these public interfaces are accessed through the Index they provide. The main modules used by the animation system are com and gfx. The following sections describe the usage of them. 3.1 The com Module The com module is the most elemental one defining c o m m o n functionality required across the whole engine. Here is where the implementation of the module base class can be found. Age operates on a so-called Context, w h i c h is responsible for the storage and organization of runtime information. It is a tree structure of connected Contextltems objects arranged hierarchically. Similarly to h o w physical storage is usually organized, the Context contains a structure of nested folders, where each of the leaf nodes is either a File or a Link. Folders are collections, files represent data and links are pointers to files that exist elsewhere i n the hierarchy. This makes it easy to access the same data from various places without the need to duplicate them. One of the structures the animation system is the most reliant on, is the Frame class, which represents position, orientation and scale i n 3D space. It utilizes the hierarchical structure of the Context, and stores the spatial disposition relative to its parent. The parent is designated 33 3. A G E via a link called frame_parent. l i n k placed i n the same folder as the frame. This comes especially useful d u r i n g the implementation of skeletal animations and skinning, as described i n the later Section Runners are a specialized file, as they have a next_round() method that is ensured to be r u n on each frame update. This is essential for time-sensitive calculations that need to be executed on each render, such as physics computations or animation system updates. They can be either classified as an updater , w h i c h are the objects only m o d i f y i n g the Context, or as a presenter , w h i c h also produces an output. Reflection is the ability of a p r o g r a m m i n g language to m o d i f y its o w n constructs. A l t h o u g h static reflection is planned, C + + does not possess runtime reflection abilities However, A g e provides its o w n reflection system that is useful for both the upcoming scripting engine and the animation system. For a method to be accessible i n the reflection system, it has to be manually registered i n a central repository of callable functions the following way: R e f l e c t i o n : : r e g i s t e r _ f u n c t i o n ( t h i s , { " s e t _ o r i g i n " , { TypelD::VEC3 }, f a l s e , [this](std::vector const& params) -> void { set_origin(as(params.front())->value); } }); • com:: Contextltem is the abstract parent class for all items that can exist i n the Context • com::Frame represents position, orientation and scale i n 3 D space • com: :Folder a collection of com: :Contextltem s • com::File storage • com:: MathFile can be used for storing a single instance of a numerical or more complex type • com:: Link a pointer to a file somewhere else i n the hierarchy 34 3. A G E • com:: Runner is an entity that is invoked o n every frame update root/ | - - o s i / I I - - window | j - - keyboard | j - - mouse | j - - timer | j - - runners/ | |- - updaters/ | | |-- gfx_ui_updater.run.link | | j - - anim.run.link | | j - - game/ | | |-- updater.run.link | j - - presenters/ | |- - game/ | |-- presenter.run.link |-- l f s / | |-- data/ | j - - l o a d e r . l i b | j - - updater.run I - - gfx/ | |-- shader_system.lib | j - - shaders/ | | |-- active_shader.link | | j - - b u i l t - i n - s h a d e r s / | | |-- forward-unlit-vertex-color-shader | | j - - d e f a u l t - l i t - s h a d e r | j - - material_system.lib | j - - materials/ | | |-- a c t i v e _ m a t e r i a l . l i n k | | j - - game/ | | |-- g r i d _ m a t e r i a l / | | | | - - uniforms/ | | j - - box_material/ | | |- - uniforms/ | | |- - c o l o r | j - - buffer_system.lib | j - - b u f f e r s / | | |-- a c t i v e _ b u f f e r . l i n k | | j - - procedural/ I I I I " g r i d | | |- - game/ | | | - - test_box | j - - b u f f e r _ g e n e r a t o r s . l i b | j - - viewport_system.lib | j - - viewports/ | | |-- d e f a u l t | | j - - a c t i v e _ v i e w p o r t . l i n k | | j - - console | j - - object_system.lib | j - - objects/ I I I - - g r i d / I | | | - - m a t e r i a l . l i n k | | | |-- b u f f e r . l i n k | | | j - - frames/ | | | | |-- l i n k . l i n k | | j - - game/ | | | - - test_box/ | | | - - m a t e r i a l . l i n k | | j - - b u f f e r . l i n k | | j - - frames/ | | I I - l i n k . l i n k | | I I - - l i n k O . l i n k | | I I - - l i n k l . l i n k | | I I - - l i n k 2 . 1 i n k | | j - - l i g h t s / | | |-- d i r e c t i o n a l . l i n k | - - game/ |-- camera/ | |- - frame j - - box/ | |- - frame | j - - object/ 35 3. A G E I |- - frame I - - frame_parent.link | - ... Since even the specialized objects inherit f r o m Folder or File, all of them are plain C + + objects. This means that there is a fine border between what needs to be explicitly represented as a separate object i n the Context, and what is enough to store inside one of the specialized classes' member variables. A s a rule of thumb, by turning some data into a separate file, the intent of m a k i n g them publicly available for access is stronger. It is left to the judgment of the developer to think through h o w likely it is that other users w o u l d want direct access to the given data, and publishing it as a separate object or by providing publicly accessible methods is the more straightforward w a y i n the given use case. 3.2 The gfx Module The gfx module is responsible for managing and rendering objects i n the scene w i t h all of the surrounding work: shaders, materials, buffers, cameras, lights and their configurations. • com:: Contextltem is the abstract parent class for all items that can exist i n the Context • com::Frame represents position, orientation and scale i n 3 D space • com: :Folder a collection of com: :Contextltem s • com::File storage • com:: MathFile can be used for storing a single instance of a numerical or more complex type • com:: Link a pointer to a file somewhere else i n the hierarchy • com:: Runner is an entity that is invoked on every frame update To display anything on the screen, so-called shader programs are required. The most commonly used shaders are the vertex shader that 36 3. A G E operate on the input of vertices, and the fragment shader w h i c h is responsible for rasterization and is called for every pixel. By grouping these shaders and mathematical representations of physical properties, materials are created. The material assigned to the mesh defines h o w the object is displayed. These materials often make use of variables shared between the various shader programs, w h i c h are called uniforms. The position and other bundled properties of vertices, such as the normal vectors required for lighting, are stored i n buffers. Meshes are displayed by rendering triangles between adjacent vertices. Presenting an object i n Age can be done by first inserting a new object into the existing hierarchy of the object system by calling insert _obj ect () , and assigning a path that serves for referencing the object throughout its existence, and the above-described pair of material and buffer. To place the object into the scene a Frame item needs to be created inside of the object's folder, which w i l l define the position and orientation at w h i c h the object needs to be rendered. 3.3 Other Modules A t the time of writing, the rest of the modules available i n A g e are i f s , math , osi and u t i l s . A l l of them were utilized during the implementation of the animation module, but they provide more specific functionality. The abbreviation i f s stands for Local File System, and allows access to files on the disk. It is makes it possible to load data asynchronously, meaning that the process is not waiting indefinitely until the file has been loaded into memory, but instead just issues the comm a n d to load the data, the C P U proceeds to w o r k on other tasks, and returns to the data only after receiving the notification that the data have been loaded. This is the desired behavior, as we never want to block the m a i n thread of the application, since it w o u l d become unresponsive to user input, which w o u l d cause a bad user experience. The role of the math module is to provide an abstraction above math libraries. This abstraction is achieved by using custom data types, which are then mapped to the structures provided by the library. This means that the engine is not tightly coupled to the mathematical 37 3. A G E constructs, and the backend can be easily replaced w h e n desired. Currently, Age provides bindings to G L M , Eigen3, and U B L A S . The osi module does something similar, but it provides an abstraction above the operating system. It is responsible for initializing the graphics backend, w i n d o w creation and lifetime management, while also exposing a high-level interface through the Context for detecting user input, such as keypresses or mouse movement, and time tracking, by providing timers keeping track of the time elapsed since application startup or since the previous frame. Finally, u t i l s is the home of general utility functions, w h i c h are often needed and can come i n handy i n any of the modules. By having a central repository, the code does not have to be duplicated. Functionality required during development and helpful while debugging is also located here, such as the time measurement macros used for profiling or the print_tree() function, w h i c h displays the whole context tree. For further details on the inner workings of Age, please see the work of Vincze [10], Babic [11], and Petr [12]. 38 4 Implementation This chapter builds on the theoretical background from the first chapter, and describes how the anim module has been designed to fit into the existing structure of Age, while considering the best practices of the industry. 4.1 Design Considerations The main points kept in m i n d during the implementation of the animation system have been the extensibility of the system, and keeping it as loosely coupled as possible. It is important to support the animation of any numerical property. A n i m a t i o n is broad topic, and is impossible to implement everything w i t h i n the scope of a master's thesis, so it was designed to be scalable, and the possible extensions and r o o m for improvement have been kept in mind. Although the development of an editor frontend was not part of the assignment, a good user experience w i l l be integral to the success of the system, so this had to be kept i n m i n d as well. 4.2 Minimal Example Let us consider the minimal example of setting up an animation in Age. To do so, we need an animation player that has an animation assigned, which contains a track w i t h at least two keyframes. Assuming that we have access to the frame of an existing object i n the scene w o u l d look like this: auto test_anim = anim::animation_system()->insert_animation_with_player(" test_anim"); test_anim->add_track( "test_move_x", frame, " s e t _ o r i g i n _ x " , anim::Keyframes{ {0, 0.f}, {1, l . f } } ); 39 4. IMPLEMENTATION This code creates an animation based on the name " t e s t a n i m " , and assigns it to an animation player immediately. The command after that constructs an animation track called " t e s t m o v e x " , marks its target as the set_origin_x() method of the frame contextitem.lt also creates a sequence of two keyframes, by assigning O.f at time 0, and setting x to 1./ at timestamp 1. Please, see the result of this code in the attached Video 1. The hierarchy of the anim folder is very simple i n this case, and it is easy to pinpoint the above-described constructions: anim/ | - - animator.run J - - animation_system.lib J - - animations/ J |-- test_anim.anim/ I |-- test_move_x.track j - - players/ |- - test_anim.player/ 4.3 Building Blocks This section presents the classes that form the backbone of the animation system. 4.3.1 Keyframe Each keyframe stores its o w n timestamp, associated value, and an Interpolator instance used for calculating the values between this and the following keyframe. The value is an s t d : : v a r i a n t , making it possible to store different types of values i n a type-safe way. 1 using ValueType = std::variant; Using the variant for storing the value, it is easy to ensure the correct overloaded version of a function is called depending on the type of the underlying value, by applying the visitor pattern. The operator () of the struct i n t e r p o l a t e V i s i t o r is a templated method, however, w h e n called on a concrete type that compiler is able to substitute i n that exact type, and call the correct l e r p O function. In most cases, this procedure works similarly, however, for both booleans (which 40 4. IMPLEMENTATION Table 4.1: A comparison of std: : variant and std: :unique_ptr for storing keyframe values s t d : : v a r i a n t std::unique_ptr Advantages • type-safe • value stored within the Keyframe • easy to copy • efficient memory-usage • dynamic types Drawbacks • wasting memory • heap allocation overhead • complex type checks • heap allocation • dereferencing • potential dynamic casts cannot really be interpolated) and quaternions (on which we want to rather apply slerp () as described earlier) it has a specialized variant that the compiler picks correctly. This compile-time polymorphism is advantageous because it does not cause any runtime overhead. Another possible implementation could have been to store just pointers to the value of the keyframe. Since the variant needs to allocate such amount of memory that even the greatest variant type w o u l d fit into it, this leads to somewhat higher memory consumption than what is necessary. O n the other hand, the data is stored right in the Keyframe objects, w h i c h leads to easier access. Unique pointers w o u l d have an advantage regarding runtime flexibility and p o l y m o r p h i s m as well, but that w o u l d come w i t h the necessity of allocating small containers on the heap, it w o u l d complicate ownership management, it requires dereferencing and could potentially introduce the requirement to rely on dynamic_cast too much. It has been decided for the variant, due to the type safety, simplicity, and lower m e m o r y overhead it provides. The variants it needs to represent do not differ enough i n size to cause a significant memory usage problem. Table provides a s u m m a r y of the advantages and disadvantages of both approaches. 41 4. IMPLEMENTATION 4.3.2 Interpolation The keyframes make use of the strategy design pattern for storing their interpolation method. This technique delegates the information necessary for calculation to a separate class responsible for doing the computing. In the interpolator .hpp file an abstract base class is defined, f r o m w h i c h the concrete implementations are inherited. They share a common interface, but define their o w n algorithms. This makes it simple to modify any of the existing algorithms or introduce new ones without needing to touch any other file. According to the assignment, constant, linear, and Bezier cubic interpolators have been implemented i n this thesis. In addition, inspired by Godot's simple easing interface, an easing interpolator has also been implemented. The Constantlnterpolator is as simple as the mathematical definition looked i n Section . It returns the same value across the whole segment between two adjacent keyframes. The Linear Interpolator is simple as well, as it calculates the linear combination of the two values. The implementation includes a specialization for booleans, i n w h i c h case the interpolator essentially behaves the same as i n the constant case, and also for quaternions, w h i c h are interpolated using Slerp. The Bezierlnterpolator is, however, a little more complicated than the previous two variants. A s described i n the theoretical chapter, Bezier interpolation takes four points as its input. The two border points are formed by the keyframes themselves (with x being the time, and y being the corresponding value), whereas the two middle points are calculated by adding the relative offsets stored in the BezierHandles member variable. These four points are n o w parametrized with an u n k n o w n parameter u we need to find out. We k n o w the time t we are solving for, so the task is to find out what parameter gives a point w i t h its x coordinate equal to t. If we k n o w u , finding the corresponding y value is trivial. One way to find out u is to look at the system of equations analytically, and solve it using Cardano's method. This involves transforming the cubic equation into its depressed form, and based on the sign of the discriminant, we are able to differentiate between three cases: • discriminant > 0: one real root 42 4. IMPLEMENTATION • discriminant = 0: triple root • discriminant < 0: three real roots (out of which we are interested in the one lying between 0 and 1) By determining the root u , we just need to evaluate the curve at u to get the interpolated value at time t . Another way to approximate the root of the equations is by using the Newton-Raphson iterative method. In this algorithm, the curve and its derivative need to be evaluated w i t h an assumed root, and this guess is refined w i t h each iteration by subtracting the derivative scaled by the approximation's distance f r o m the target value. The advantage of this method is that it does not require evaluating square roots and trigonometrical functions, w h i c h are generally considered not the most ideal task for computers. O n the other hand, however, it is numerically less stable due to its iterative approach, and the speed of convergence is not guaranteed. For a performance evaluation of the two methods, please see Section. The Easinglnterpolator is a linear interpolator with a twist: it operates by applying an easing function on the time parameter and calls the linear interpolator w i t h this eased argument. The implementation is based on Godot's solution, w h i c h operates w i t h a single easing factor. If the factor is greater than 1, ease-out is achieved, whereas between 0 and 1 ease-in is produced. The further the value is f r o m 1, the more drastic the effect. If t — 1, the result is a simple linear interpolation. O n the other side of 0 two exponential functions are pieced together to form ease-in-out whenever the value is smaller than —1, and ease-out-in if the parameter is between 0 and —1. 1 s c a l a r Easinglnterpolator::ease(scalar t ) const { 2 i f (m_easingFactor > 0) { 3 i f (m_easingFactor < 1.0) { // Ease i n 4 return 1.0_s - std::pow(1.0_s - t , 1.0_s / m_easingFactor); 5 } else { // Ease out 6 return std::pow(t, m_easingFactor); 7 } 8 } else i f (m_easingFactor < 0) { // Ease in/out and out/in b u i l t from two segments 9 i f (t < 0.5) { 10 return std::pow(t * 2.0_s, -m_easingFactor) * 0.5_s; 11 } else { 12 return (1.0 - std::pow(1.0 - (t - 0.5) * 2.0, m_easingFactor)) * 0.5_s + 0.5_s; 13 } 43 4. IMPLEMENTATION } return t ; //No easing ( l i n e a r ) } The concrete implementations can easily store the extra information required for their computations, not needed by any other of the implemented interpolators. The Bezierlnterpolator makes use of this by storing the control handles for the given segment, whereas the Easinglnterpolator requires an easing factor for specifying the acceleration and deceleration speed. 4.3.3 Animation Track The AnimationTrack class represents a collection of keyframes associated w i t h a specific method of a specific context item instance. It is responsible for keeping the keyframe vector always sorted. The class inherits from com:: Link , and this approach ensures that tracks cannot be created without pointing to an existing item. By calling the get_value (scalar t) method, the interpolated value at time t can be retrieved. The return type of this method is ValueMap , which is an unordered map w i t h com: :Reflection: :Function* pointers as keys, w h i c h have the actual value stored i n the ValueType variant. It also provides interfaces to insert, delete, and update keyframes. Similarly to keyframes, this class went through several iterations of experimentation as well, before ending up w i t h this final solution. It has been tried here as well to store only vectors of pointers to keyframes, but by utilizing variants, the same keyframe type can be used independently of the type of the value stored, so it was more straightforward to store keyframes directly as well. Another interesting approach could have been to use an ordered map for the keyframes, and the key could have been the timestamp of the keyframe. However, modifying keys of a map, even if not completely impossible, can be quite cumbersome because of their i m mutable nature. A t the same time, manipulation w i t h the keyframes, including reordering, is not performance critical since that usually does not happen dynamically i n real time, but only i n the editor during the preparation. 44 4. IMPLEMENTATION 4.3.4 Animation The Animation class is responsible for bundling into an animation clip all the animation tracks that need to be played synchronized. Usually, a single animation targets one character or entity i n the scene, but technically, there is nothing blocking the user from targeting multiple objects at once. It can also designate the start and end of the clip, selecting just a portion of the time span covered by the keyframes, or extending it longer than the difference between the bordering keyframes. The length is independent of the extent of the tracks; the first and last keyframes are just constantly interpolated (i.e., extended) outside of the range. It implements the I Animation interface, w h i c h encompasses all the structures that have a length and can be asked to return an array of values for a given timestamp. 4.3.5 AnimationPlayer The animations i n themselves are still just containers, however, and the classes inherited from AnimationPlayer are the ones that represent the core logic of manipulating animations. They control the playback of animation clips. They collect the interpolated values f r o m their children, process them i n any way necessary, and then there is nothing else left but to apply the changes to the targeted properties. There can be more clips assigned to the player, but most often only one of them is actively played. The player is responsible for playing, pausing, looping, and reversing the animations, and also the classes inherited from it handle the process of replacing the active clip, transitioning seamlessly between them, or blending them partially. The speed of the playback can also be adjusted. The AnimationPlayer was designed w i t h extensibility i n mind. A s stated i n the overview section, animation behaviors are often built using node based graphs, blend spaces and state machines. Their common property is that they operate on an input of variable data, and for example mix the output from two animation clips, but then produce the same output or something i n a similar fashion. A l t h o u g h these advanced constructions exceeds the scope of this work, by inheriting these animation modifiers from the same player base class, it w i l l not be difficult to create chains of animation players. 45 4. IMPLEMENTATION 4.3.6 Animator The Animator is just a simple runner. Its only role is to traverse the context tree, keep track of and trigger the animation players on each frame update. During execution, it forwards information to the players about the time passed since the last update. 4.3.7 Scalar To further enhance the decoupling from the mathematical libraries, a custom type called scalar is used across Age instead of the built-in floating point types. Although it is just a type alias for f l o a t , by using this alias everywhere, w h e n the need arises to increase the precision, it w i l l need to be changed only at a single place. Since the type alias can be used only w h e n defining variables or using casts, it can still happen that after changing the underlying type, the compiler could produce a lot of warnings concerning mismatched precision. This can be overcome by utilizing the user-defined literals offered by C + + . B y defining the literal suffix _s , we can make it explicit that the given literal is of type scalar . This is a good practice, motivating the user to use casting more consciously. using s c a l a r = f l o a t ; constexpr s c a l a r operator""_s(long double value) { return static_cast(value); } 4.4 Skinning and Skeletal Animation A s described i n Section, skeletal animations form an integral part of game engine animation systems, and for this reason Age's animation module also provides this functionality. One of the first changes required for skinning was to extend the gf x module. A l t h o u g h it already p r o v i d e d a w a y to assign bone weights to vertices i n the Buf f er , a new shader node VaryingBoneNode and the corresponding type BonesVec h a d to be introduced. Since usually there are m a n y bones i n a skeleton, but only a few of them have an impact on the same vertex, this four-component vector of 46 4. IMPLEMENTATION unsigned integers is used to store the IDs of the bones that the four components i n the weight vector correspond to. In practice, four is often the number of bones that can have an influence on a single vertex, w h i c h is satisfactory i n general, apart from a few more complex situations, such as detailed facial rigs. Since skinning happens mostly i n the vertex shader, special ShaderGraph nodes had to be implemented as well. SkinningUnlitNode is the simpler base class of the two, as it calculates only the skinned position of the vertex based on its initial position, the above-mentioned weight and bone vectors, and the bone matrices accessed through a uniform buffer object. Nodes inherited from SkinningNode , however, must also calculate normals and tangents, so that textures a lighting w o u l d be correctly applied. The code of LinearBlendSkinningUnlitNode doing the actual skinning is very simple, since it just computes the weighted linear combination of four transformed points: vec3 get_skinned_position(vec3 p o s i t i o n , vec4 weights, uvec4 bone_indices) { vec3 r e s u l t = vec3(0); f o r ( i n t i = 0; i < 4; ++i) // We assume 4 weights per vertex. r e s u l t += vec3(weights[i] * bones[bone_indices[i]] * vec4(position, 1)); return r e s u l t ; }) The LinearBlendSkinningNode is not complicated more by much, only the computations have to be done three times. However, to set up a mesh w i t h skinning weights manually is pretty tedious. To actually be able to test if skinning works, a simple glTF importer has been implemented. The glTF format is a royalty-free 3D file format i n the care of the same Khronos Group, w h o are responsible for O p e n G L . It has both a JSON-based textual variant with the . g l t f extension, and the binary . gib . Since this was not the main aspect of the thesis, to speed up the development, the header-only TinyGLTF library has been utilized. To demonstrate setting up skinning, let us use a simple elongated bar w i t h a simple skeletal hierarchy of a root bone i n the centre, one bone on the left, and one bone on the right side The following snippet imports the bar from the disk on line 1. We locate the frame representing the right bone i n the context hierarchy, 47 4. IMPLEMENTATION and then continue w i t h configuring the animation the usual way - this time targeting the rotation property. Since the importer automatically assigns a lit shader, we locate the object i n the object system and register the directional light. Please, see the output i n Video 2. 1 u t i l s : : i m p o r t _ g l t f ( r o o t ( ) , "data/age/rigged_cube.gltf", " g l t f _ b o x " ) ; 2 auto frame = root()->locate( 3 {"gltf_box", "Armature", "Bone.Root", "Bone.R", "frame"}); 4 auto bone_animation = anim::animation_system() 5 ->insert_animation_with_player("bone"); 6 bone_animation->add_track( 7 " r i g h t . r o t a t i o n " , 8 frame, 9 " s e t _ r o t a t i o n " , 10 anim::Keyframes{ 11 {0, 0._s}, 12 {2, -Num::half_pi}, 13 {4, 0._s}, 14 } 15 ); 16 17 auto box = gfx::object_system()->objects()->locate( 18 {"game", "gltf_box gltf_box_Cube.0010"}); 19 gfx::object_system()->insert_light(box, d i r _ l i g h t ) ; Hierarchy of the imported object i n Blender. 48 4- IMPLEMENTATION Below you can see an excerpt archy of the above program. root/ I -- gfx/ | |-- shader_system.lib | j - - shaders/ | | |-- active_shader.link | | j - - b u i l t - i n - s h a d e r s / | | |-- forward-unlit-vertex-color- shader | | |-- d e f a u l t - l i t - s k i n n i n g - s h a d e r | j - - material_system.lib | j - - materials/ | | |-- a c t i v e _ m a t e r i a l . l i n k | | j - - game/ | | |- - box_material/ | | | - - uniforms/ | | |- - color | | j - - bones | j - - buffer_system.lib | j - - buffers/ | | | - - game/ | | |-- gltf_box_Cube.0010 | j - - object_system.lib | j - - objects/ | | |- - game/ | | |-- gltf_box gltf_box_Cube.0010/ | | |-- m a t e r i a l . l i n k | | j - - b u f f e r . l i n k | | j - - frames/ | | I I - l i n k . l i n k | | j - - uniforms/ | | j - - l i g h t s / | | |-- d i r e c t i o n a l . l i n k | j - - l i g h t _ s y s t e m . l i b | j - - l i g h t s / | | |-- g l o b a l / | | |- - d i r e c t i o n a l / | | |-- shadow_casters/ | | j - - frame.link I I I - - l i g h t | j - - camera_system.lib of relevant structures from the hier-- anim/ |- - animation_system.lib |- - animations/ | |-- bone.anim/ | |-- r i g h t . r o t a t i o n . t r a c k j - - players/ | |-- bone.player/ | - - skeletons/ |-- g l t f _ b o x / |-- Armature/ |-- Bone.Root/ | |- - inverse_bind |-- Bone.L/ | |- - inverse_bind |-- Bone.R/ |- - inverse_bind -- game/ |-- gltf_box/ |- - frame |- - Armature/ |-- frame |-- frame_parent.link | -- Cube/ | |- - frame | j - - frame_parent.link | -- Bone.Root/ |- - frame |- - frame_parent.link |-- Bone.L/ | |- - frame | j - - frame_parent.link | j - - j o i n t . i n d e x | j - - inverse_bind.link |-- Bone.R/ | |- - frame | j - - frame_parent.link | j - - j o i n t . i n d e x | j - - inverse_bind.link | - - j o i n t . i n d e x j - - inverse_bind.link As displayed above, the hierarchy can easily become overcrowded w i t h all these auxiliary files required to p u t skinning into operation. gf x/materials/game/box_material is the folder of the bar's material. A s y o u can see, there are two uniforms associated w i t h it: color , w h i c h is a com: :FileVec3 a n d contains the color of the object; a n d also bones , which is a vector of matrices stored as VectorMat4x4 containing the current transformation for every bone. These files represent the interface between the object system and the renderer, as it takes a look into these files o n every frame update. The renderer loops through the uniforms looking for one named bones , and then calls calculate_bone_matrices() to update the transformations. To calculate the matrices, w e need to "subtract" the bind pose transformation from the current pose by multiplying it w i t h the inverse of the b i n d pose. The glTF files contain directly the inverse b i n d matrix, a n d the importer automatically creates MathFile s to 49 4. IMPLEMENTATION store them i n the context. A s you can see, there is always one parent frame added above the root node imported from glTF. This frame is used to position the m o d e l i n w o r l d space. A l t h o u g h it is automatically included i n the transformations of its child nodes, we want to exclude it, since the global position should not have any impact on the skinning process. To achieve this, we multiply the partial result by the inverse of this parent frame's transform. The final computation can be written as: 1 auto mat = root_frame->in() * bone_frame->out() * inverse_bind; These matrices are then loaded into a uniform buffer object, which can then be directly accessed from the shader. Their advantage over ordinary u n i f o r m arrays is the higher capacity, however, they still require the number of elements to be k n o w n i n advance. SSBOs, or Shader Storage Buffer Objects, could present an alternative, as they can contain a variable number of elements, but they are slower compared to U B O s . In practice, professional game engines, however, rely on an entirely different construct: they utilize textures for sending bigger amounts of data to the G P U i n bulk, and they are designed for parallel access. This could be considered i n the future for Age as well. The importer is able to load multiple animations targeting the same skeleton as well. In fact, this is the simplest way to configure animations with blending. By exporting an object w i t h multiple animations from the modelling program, Age imports them as separate animation clips and assigns the first one to an animation player. 4.4.1 Simple curve editor D u r i n g the writing of the thesis, an application w i t h a simple user interface emerged as a side effect. It has been used throughout the development process to assess the practicality of the interface provided by the animation system. This approach helped to test the numerical correctness of interpolation, as well as made it possible to use it for rapid prototyping of the constructs of the animation system. This application uses Dear I m G u i for the U I , and as such, cannot be incorporated into Age directly, since for that purpose, Age's o w n U I module should be utilized. Due to time constraints, it was not possible to refactor it under the built-in U I system, but it is still p r o v i d e d 50 4. IMPLEMENTATION as an attachment to this thesis w i t h the hopes that the interactive nature might make the testing of the system a more user-friendly and engaging experience, while also inspiring future developers of Age. 51 5 Evaluation To assess the performance of the implementation, it has gone through benchmark testing. Two test applications have been included i n the appendix, as one focuses on curves and interpolation i n general, whereas the other targets skinning and skeletal animation. The test have been executed on a desktop P C w i t h an A M D Ryzen 9 5900X 12-core C P U and an N V I D I A GeForce RTX 3070 graphics card on board. 5.1 Evaluation of the Interpolation Algorithms Table 5.1: Result after measuring curve _benchmarks Function GenuineDuration Speed-up anim: A n i m a t o r : :next_round 27.920 1.000 anim::AnimationPlayer::apply_values 27.899 17.978 gfx::Renderer::present_collection 19.500 1.000 anim: :'anonymous-namespace': :solveBezierCardano 11.277 1.140 anim::EasingInterpolator::interpolate 10.741 0.755 anim: :'anonymous-namespace': :solveBezierX 4.454 0.791 gam: :Presenter: initialize 4.077 1.000 anim::LinearInterpolator::interpolate 4.020 0.602 anim::ConstantInterpolator::interpolate 3.063 0.530 com::ContextItem::is_under 0.003 1.000 10 112.953 This test has been done by populating the scene w i t h a grid of cubes. Each of them h a d three different animations assigned. One animation modified the z coordinate of the object's parent frame, thus interpolating a scalar value, the second manipulated the vec3 coordinates of the object, whereas the last one was a quaternion-based interpolation of rotation. The results were not shockingly surprising. The fastest algorithm is the constant interpolation closely followed by linear interpolation. What may come as a surprise though is that the iterative approach of 52 5- EVALUATION calculating Bezier interpolation is even faster than the easing function, but even that is significantly faster than solving the cubic equation using Cardano's formula. This confirmed the conjecture, w h y major game engines mostly resort to using the Newton-Raphson iterative method. 5.2 Evaluation of Skeletal Animations The result of the second application were not surprising either. The computer was able to keep up with the rendering of a lot less skinned characters than cubes. This however does not necessarily lead to the conclusion that Linear Blend Skinning is unperformant. It is more likely that the difference we see i n the numbers is caused merely by the fact that a single character had over 60 bones, each w i t h position and rotation sampled every second. This means that one of the skinned characters represent and equivalent stress for the P C compared to of the basic cube primitives w i t h some animations assigned. However, the application used significant amounts of memory, mostly because of the limited abilities currently offered by the glTF importer to reuse already loaded models. The loading times also increased significantly w i t h each increase of the number of characters placed into the scene. Table 5.2: Result after measuring skeletal_benchmarks Function GenuineDuration Speed-up anim::Animator: :next_round 49.562 1.000 anim::AnimationPlayer::apply_values 49.559 79.124 anim::LinearInterpolator::interpolate 42.523 1.381 gam: :Presenter: :initialize 25.248 1.000 gfx::Renderer::present_collection 1.451 1.000 gfx: :calculate_bone_matrices 0.029 9.694 com::ContextItem::is_under 0.001 1.000 7 168.372 It can be seen from the table that AnimationPlayer's apply_values is parallelized. 53 6 Conclusion This thesis aimed to design and implement an animation module i n Age. Before implementation there has been a lot of work surrounding market research to get a picture of the solutions already existing on the market. In terms of functionality, the implementation provides a way to interpolate any value present i n the Context by utilizing the reflection features built into the engine. The module is able to interpolate between numerical values using various methods, provides functionalities for skeletal animation and skinning, and makes it possible to blend between animation clips. Incidentally a simple glTF importer utility and a simple curve editor has also been implemented. The included applications showcase the functionality i n the form of tutorials and templates, while the output of benchmark applications mark out the limits of the implementation. 6.1 Future Animation is a big topic, and there are a lot of areas this module can be improved in. Since animations are resource intensive, mainly from a storage perspective, it w o u l d make sense to further look into animation compression techniques. Firing events f r o m animations w o u l d also be an obvious upgrade. A s described i n the overview chapter, modern game engines use various components, such as blend spaces or state machines that help to produce more believable behaviors i n the digital realm. The current design makes it possible to extend the engine i n this direction easily, so it w o u l d be w o r t h exploring these possibilities. The integration w i t h i f s should not be forgotten about either. Perhaps the possibility the save or serialize animation w o u l d be a welcome development. M o d e r n engines rely on and offer more possibilities regarding inverse kinematics then ever before. Age should also be tailored to the development of real-time multiplayer games, and the synchronization of animation over network should not be an overlooked detail either. 54 A An appendix For all four of these applications, both the source code and a Windows x64 b u i l d are available. • a n i m a t i o n _ t u t o r i a l 1 : A n application designed to be the first encounter of new users with Age's animation module. The user has to gradually uncomment the lines i n presenter. cpp . • curve _benchamrks 2 : A n application instantiating cube p r i m i tives, w i t h three animations each assigned. • skeletal_benchmarks 3 : A n application loading a lot of skinned humanoid characters from glTF. Benchmark results presented i n Chapter 5. • age_animation : A n application w i t h a simple curve editor built w i t h Dear I m G u i showcasing some of the functionality. - The Blend button toggles the wave animation. Whenever it is clicked, the arm animation is transitioned to the other position. - The Jump can be used to insert a new keyframe to the currently edited animation track. - The radio buttons should change the interpolation method of the keyframe selected. - The blend slider blends a partial animation of gunshots into the animation of walking. Furthermore, the following videos can be found i n the videos folder: 1. Also available at: https://gitlab.fi.muni.cz/gamedev/age/tutorials/ animation/animation_tutorial 2. Also available at: https://gitlab.fi.muni.cz/gamedev/age/benchmarks/ animation/curve_benchmarks 3. Also available at: https://gitlab.fi.muni.cz/gamedev/age/benchmarks/ animation/skeletal_benchmarks 55 A . A N APPENDIX 1. set_origin_x.mp4 Displays a minimal example of a simple animation being applied. 2. skinning_minimal. mp4 : Showcases a simple skinning scenario. 56 Bibliography 1. N I C O G U A R O . Rungejphenomenon.svg [online]. [N.d.]. [visited on 2025-05-21]. Available from: https : / /upload . wikimedia . org/wikipedia/commons/0/Oa/Runge_phenomenon.svg. 2. B A R C Z A K , Joshua. Splines Are Just Obfuscated Beziers - The Burning Basis Vector [online]. 2015. [visited o n 2025-05-21]. Available from: h t t p : //www. j oshbarczak. com/blog/?p=730. 3. S H O E M A K E , Ken. Animating rotation w i t h quaternion curves. ACM SIGGRAPH Computer Graphics [online]. 1985, vol. 19, no. 3, pp. 245-254 [visited o n 2025-05-21]. ISSN 0097-8930. Available from DOI: 10.1145/325165.325242. 4. nlerp — GIANT 1.0.0 documentation [online]. [N.d.]. [visited on 2025-05-21]. Available from: https : //aliounis . github . io/ giant _documentation/rotations/giant . rotations . nlerp . html. 5. Method nlerp \ Mathematics \ 1.2.6 [online]. [N.d.]. [visited on 2025- 05-21]. Available from: https: //docs .unity3d. com/Packages/ com. unity . mathematics01. 2/api/Unity . Mathematics . math. nlerp.html. 6. The Big Game Engines Report of 2025 [online]. [N.d.]. [visited o n 2025-05-21]. Available from: https : //vginsights . com. 7. T E C H N O L O G I E S , Unity. Unity - Manual: Unity 6.1 User Manual [online]. [N.d.]. [visited o n 2025-05-21]. Available from: https://docs.unity3d.com/6000.1/Documentation/Manual/ UnityManual.html. 8. Unreal Engine 5.5 Documentation \ Unreal Engine 5.5 Documentation | Epic Developer Community [online]. [N.d.]. [visited o n 2025-05-21]. Available from: https : //dev . epicgames . com/ documentation/ en- us /unreal - engine /unreal - engine - 5 - 5- documentation. 9. Introduction to the animation features — Godot Engine (stable) documentation in English [online]. [N.d.]. [visited o n 2025-05-21]. Available from: https : //docs . godotengine . org/en/stable/ tutorials/animation/introduction.html. 57 B I B L I O G R A P H Y 10. V I N C Z E , Filip. Graphical user interface for Age. Brno, 2025. Available also from: h t t p s : //is .muni . cz/th/xt3s9/. 11. B A B I C , Petr. Shader graph module for Age. Brno, 2024. Available also from: h t t p s : //is .muni . cz/th/zdf 8h/. 12. PETR, Michal. Collision detection module for Age. Brno, 2025. Available also from: h t t p s : / / i s .muni . cz/th/e66wm/. Diplomová práce. Masarykova univerzita, Fakulta informatiky. Supervised by Marek TRTÍK. 58