From 2842b21f72a2caeef732d6d7732b4aa878c9b17a Mon Sep 17 00:00:00 2001 From: skyace65 Date: Sun, 11 Aug 2024 19:25:07 -0400 Subject: [PATCH] Add physics interpolation docs to 4.3 --- tutorials/physics/index.rst | 1 + .../2d_and_3d_physics_interpolation.rst | 72 ++++++ .../advanced_physics_interpolation.rst | 185 ++++++++++++++ .../img/fti_camera_worldspace.png | Bin 0 -> 7678 bytes .../img/fti_graph_fixed_ticks.png | Bin 0 -> 10788 bytes .../img/fti_graph_interpolated.png | Bin 0 -> 27955 bytes .../img/physics_interpolation_mode.webp | Bin 0 -> 5714 bytes tutorials/physics/interpolation/index.rst | 14 + .../physics_interpolation_introduction.rst | 241 ++++++++++++++++++ ...hysics_interpolation_quick_start_guide.rst | 15 ++ .../using_physics_interpolation.rst | 158 ++++++++++++ 11 files changed, 686 insertions(+) create mode 100644 tutorials/physics/interpolation/2d_and_3d_physics_interpolation.rst create mode 100644 tutorials/physics/interpolation/advanced_physics_interpolation.rst create mode 100644 tutorials/physics/interpolation/img/fti_camera_worldspace.png create mode 100644 tutorials/physics/interpolation/img/fti_graph_fixed_ticks.png create mode 100644 tutorials/physics/interpolation/img/fti_graph_interpolated.png create mode 100644 tutorials/physics/interpolation/img/physics_interpolation_mode.webp create mode 100644 tutorials/physics/interpolation/index.rst create mode 100644 tutorials/physics/interpolation/physics_interpolation_introduction.rst create mode 100644 tutorials/physics/interpolation/physics_interpolation_quick_start_guide.rst create mode 100644 tutorials/physics/interpolation/using_physics_interpolation.rst diff --git a/tutorials/physics/index.rst b/tutorials/physics/index.rst index d220491164b0..56d8c8433be6 100644 --- a/tutorials/physics/index.rst +++ b/tutorials/physics/index.rst @@ -19,3 +19,4 @@ Physics collision_shapes_3d large_world_coordinates troubleshooting_physics_issues + interpolation/index diff --git a/tutorials/physics/interpolation/2d_and_3d_physics_interpolation.rst b/tutorials/physics/interpolation/2d_and_3d_physics_interpolation.rst new file mode 100644 index 000000000000..68c343d4e859 --- /dev/null +++ b/tutorials/physics/interpolation/2d_and_3d_physics_interpolation.rst @@ -0,0 +1,72 @@ +.. _doc_2d_and_3d_physics_interpolation: + +2D and 3D physics interpolation +=============================== + +Generally 2D and 3D physics interpolation work in very similar ways. However, +there are a few differences, which will be described here. + +.. note:: currently only 2D physics interpolation works in Godot. + 3D interpolation is expected to come in a future update. + +Global versus local interpolation +--------------------------------- + +- In 3D, physics interpolation is performed *independently* on the **global + transform** of each 3D instance. +- In 2D by contrast, physics interpolation is performed on the **local + transform** of each 2D instance. + +This has some implications: + +- In 3D, it is easy to turn interpolation on and off at the level of each + ``Node``, via the ``physics_interpolation_mode`` property in the Inspector, + which can be set to ``On``, ``Off``, or ``Inherited``. + +.. figure:: img/physics_interpolation_mode.webp + :align: center + +- However this means that in 3D, pivots that occur in the ``SceneTree`` + (due to parent child relationships) can only be interpolated + **approximately** over the physics tick. In most cases this will not + matter, but in some situations the interpolation can look slightly *off*. +- In 2D, interpolated local transforms are passed down to children during + rendering. This means that if a parent is set to + ``physics_interpolation_mode`` ``On``, but the child is set to ``Off``, + the child will still be interpolated if the parent is moving. *Only the + child's local transform is uninterpolated.* Controlling the on / off + behaviour of 2D nodes therefore requires a little more thought and planning. +- On the positive side, pivot behaviour in the scene tree is perfectly + preserved during interpolation in 2D, which gives super smooth behaviour. + +reset_physics_interpolation() +----------------------------- + +Whenever objects are moved to a completely new position, and interpolation is +not desired (so as to prevent a "streaking" artefact), it is the +responsibility of the user to call ``reset_physics_interpolation()``. + +The good news is that in 2D, this is automatically done for you when nodes +first enter the tree. This reduces boiler plate, and reduces the effort +required to get an existing project working. + +.. note:: If you move objects *after* adding to the scene tree, you will still + need to call ``reset_physics_interpolation()`` as with 3D. + +2D Particles +------------ + +Currently only ``CPUParticles2D`` are supported for physics interpolation in +2D. It is recommended to use a physics tick rate of at least 20-30 ticks per +second to keep particles looking fluid. + +``Particles2D`` (GPU particles) are not yet interpolated, so for now it is +recommended to convert to ``CPUParticles2D`` (but keep a backup of your +``Particles2D`` in case we get these working). + +Other +----- + +- ``get_global_transform_interpolated()`` - this is currently only available for 3D. +- ``MultiMeshes`` - these should be supported in both 2D and 3D. + diff --git a/tutorials/physics/interpolation/advanced_physics_interpolation.rst b/tutorials/physics/interpolation/advanced_physics_interpolation.rst new file mode 100644 index 000000000000..be8e6ae76634 --- /dev/null +++ b/tutorials/physics/interpolation/advanced_physics_interpolation.rst @@ -0,0 +1,185 @@ +.. _doc_advanced_physics_interpolation: + +Advanced physics interpolation +============================== + +Although the previous instructions will give satisfactory results in a lot of +games, in some cases you will want to go a stage further to get the best +ossible results and the smoothest possible experience. + +Exceptions to automatic physics interpolation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Even with physics interpolation active, there may be some local situations +where you would benefit from disabling automatic interpolation for a +:ref:`Node` (or branch of the :ref:`SceneTree`), +and have the finer control of performing interpolation manually. + +This is possible using the :ref:`Node.physics_interpolation_mode` +property which is present in all Nodes. If you for example, turn off +interpolation for a Node, the children will recursively also be affected (as +they default to inheriting the parent setting). This means you can easily +disable interpolation for an entire subscene. + +The most common situation where you may want to perform your own +interpolation is Cameras. + +Cameras +^^^^^^^ + +In many cases, a :ref:`Camera` can use automatic interpolation +just like any other node. However, for best results, especially at low +physics tick rates, it is recommended that you take a manual approach to +Camera interpolation. + +This is because viewers are very sensitive to Camera movement. For instance, +a Camera that realigns slightly every 1/10th of a second (at 10tps tick rate) +will often be noticeable. You can get a much smoother result by moving the +Camera each frame in ``_process``, and following an interpolated target +manually. + +Manual Camera interpolation +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Ensure the Camera is using global coordinate space** + +The very first step when performing manual Camera interpolation is to make +sure the Camera transform is specified in *global space* rather than +inheriting the transform of a moving parent. This is because feedback can +occur between the movement of a parent node of a Camera and the movement of +the Camera Node itself, which can mess up the interpolation. + +There are two ways of doing this: + +1) Move the Camera so it is independent on its own branch, rather than being +a child of a moving object. + +.. image:: img/fti_camera_worldspace.png + +2) Call :ref:`Spatial.set_as_toplevel` +and set this to ``true``, which will make the Camera ignore the transform of +its parent. + +Typical example +^^^^^^^^^^^^^^^ + +A typical example of a custom approach is to use the ``look_at`` function in +the Camera every frame in ``_process()`` to look at a target node (such as +the player). + +But there is a problem. If we use the traditional ``get_global_transform()`` +on a Camera "target" Node, this transform will only focus the Camera on the +target *at the current physics tick*. This is *not* what we want, as the +Camera will jump about on each physics tick as the target moves. Even though +the Camera may be updated each frame, this does not help give smooth motion +if the *target* is only changing each physics tick. + +get_global_transform_interpolated() +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +What we really want to focus the Camera on, is not the position of the target +on the physics tick, but the *interpolated* position, i.e. the position at +which the target will be rendered. + +We can do this using the :ref:`Spatial.get_global_transform_interpolated` +function. This acts exactly like getting :ref:`Spatial.global_transform` +but it gives you the *interpolated* transform (during a ``_process()`` call). + +.. important:: ``get_global_transform_interpolated()`` should only be used + once or twice for special cases such as Cameras. It should + **not** be used all over the place in your code (both for + performance reasons, and to give correct gameplay). + +.. note:: Aside from exceptions like the Camera, in most cases, your game + logic should be in ``_physics_process()``. In game logic you should + be calling ``get_global_transform()`` or ``get_transform()``, which + will give the current physics transform (in global or local space + respectively), which is usually what you will want for gameplay + code. + +Example manual Camera script +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Here is an example of a simple fixed Camera which follows an interpolated +target: + +.. code-block:: python + + extends Camera + + # Node that the camera will follow + var _target + + # We will smoothly lerp to follow the target + # rather than follow exactly + var _target_pos : Vector3 = Vector3() + + func _ready() -> void: + # Find the target node + _target = get_node("../Player") + + # Turn off automatic physics interpolation for the Camera, + # we will be doing this manually + set_physics_interpolation_mode(Node.PHYSICS_INTERPOLATION_MODE_OFF) + + func _process(delta: float) -> void: + # Find the current interpolated transform of the target + var tr : Transform = _target.get_global_transform_interpolated() + + # Provide some delayed smoothed lerping towards the target position + _target_pos = lerp(_target_pos, tr.origin, min(delta, 1.0)) + + # Fixed camera position, but it will follow the target + look_at(_target_pos, Vector3(0, 1, 0)) + +Mouse look +^^^^^^^^^^ + +Mouse look is a very common way of controlling Cameras. But there is a +problem. Unlike keyboard input which can be sampled periodically on the +physics tick, mouse move events can come in continuously. The Camera will +be expected to react and follow these mouse movements on the next frame, +rather than waiting until the next physics tick. + +In this situation, it can be better to disable physics interpolation for the +Camera node (using :ref:`Node.physics_interpolation_mode`) +and directly apply the mouse input to the Camera rotation, rather than apply +it in ``_physics_process``. + +Sometimes, especially with Cameras, you will want to use a combination of +interpolation and non-interpolation: + +* A first person camera may position the camera at a player location (perhaps + using :ref:`Spatial.get_global_transform_interpolated`), + but control the Camera rotation from mouse look *without* interpolation. +* A third person camera may similarly determine the look at (target location) + of the camera using :ref:`Spatial.get_global_transform_interpolated`, + but position the camera using mouse look *without* interpolation. + +There are many permutations and variations of Camera types, but it should be +clear that in many cases, disabling automatic physics interpolation and +handling this yourself can give a better result. + +Disabling interpolation on other nodes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Although Cameras are the most common example, there are a number of cases +when you may wish other nodes to control their own interpolation, or be +non-interpolated. Consider for example, a player in a top view game whose +rotation is controlled by mouse look. Disabling physics rotation allows the +player rotation to match the mouse in real-time. + + +MultiMeshes +^^^^^^^^^^^ + +Although most visual Nodes follow the single Node single visual instance +paradigm, MultiMeshes can control several instances from the same Node. +Therefore, they have some extra functions for controlling interpolation +functionality on a *per-instance* basis. You should explore these functions +if you are using interpolated MultiMeshes. + +- :ref:`MultiMesh.reset_instance_physics_interpolation` +- :ref:`MultiMesh.set_as_bulk_array_interpolated` + +Full details are in the :ref:`MultiMesh` documentation. diff --git a/tutorials/physics/interpolation/img/fti_camera_worldspace.png b/tutorials/physics/interpolation/img/fti_camera_worldspace.png new file mode 100644 index 0000000000000000000000000000000000000000..7cacf8780d71bdab7d94ceea1be1cfdfa7bf0dad GIT binary patch literal 7678 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 Wjo#aSjW_>Z<0>m^K+5DT-~Sh_7dV>$ literal 0 HcmV?d00001 diff --git a/tutorials/physics/interpolation/img/fti_graph_fixed_ticks.png b/tutorials/physics/interpolation/img/fti_graph_fixed_ticks.png new file mode 100644 index 0000000000000000000000000000000000000000..8d484f962b70229684cb5d9a0835b7de1852b474 GIT binary patch literal 10788 zcmeHtcT|)4x^4g!XBfd5k1|SgW`b-*Q9;1aXXD6Z5YRyg#ej(PCIpCd9QKGK3JL;J zgB6q}(nAR&0vE_ZOFb zb|j!s@~y}}sdPzdpUP$JR*0{blwX@k-W&8 zgmF5%+4DCwZkAv}#Jc(zJ}|z2^QMo;!MC2Ov@{C!mwkItsQ3&2yoWi}tJ0j6l|dIN z6w2SIX7|Wb8}`nvC{)4Sc$mcXFaB8$hdfK0P^gJ-lk+ML_U$V7Uh*(Yoms^jR;_+> z{(<^l)X?6?hg@zO?%KLF{$}2xQz(?#Q7M?xzwLl1`yUR8Zqfw0KR@k{u;+p8}XDP<*kf@QO|vwV;5SlMSnC`^L^(I zB|^!*JAPfnjw3CUHI)5C`uORUf+B#{v)h%we*qJ;=kB~_6Ekm%S?Mv?)acIA(J0K1 z^i7?qB#Hd5ezp5o{t%aDYTEbM%DK%0?iTQ4cl@_Ho~OpKDaNY~1T*E(o*#GX`ktM2 zFK^`hg5+n;*8j5hHTF{P?wKbtQkdO2xU~ubqYIlFI^XXTkPnsG+kEiA-pG}Yx36Bk zihd%9{o2aPD&o@%*&HU62Bq5SFRvx57+@Mpay^k#YOjsS7-i5kX?+d%(Hz{^b4{|IBKbM>RF2*u3+3Pw`4NmP+)dZ80Xq!iuDiVnwjq4Capa1@ z!5whE+@5Us40=w*tc_>liR0D$fy6g&-h8EEaBQ3$*4dS%!(HjK(KlU%wW4`s;_U@y zHa4y~g|dS$-!^2-ByZOAOzVmc+CIGxaj##!f000Vv01T1@QV~G-oDnlHdwUYW}5F;R==-dsb^5yqeFrQjvxHD-sjN2Bf?WyXPy~ViluhdM8mPTuX z{?J9Xrqw{U^?+!DXGw__In7^!!lbl9u03+d@j05nq~g{5kQ>(NZfRW{t|U+~%!?Ay znhm`S-&f(@wlY7Uxoz9F-AB%wy-8NdDK2j|(6sMwt^}&_Y_6!`_qqE?S{+Q2wfcij zRYi>T630qyzrhkAi%99rk78xHiso^hn)w6LxW@@pLM)fJo8x&41jZ}B8 zjs}rnmRiX1)axXjn2nXL9R0&s?Fi1yV(nxsf5BCftwJ0N7L_ZbP($y11Kr46sv+Uc zZZEWBgyCRN^t4h0r(J{lK0(2As`ZchwA`{}N6F{v}K>{M0*yZKGRv17+(1e223n(^>a zbo<}F*x__J3l`e>nUh&po?$JYeAQeY^Kjiv#US);!=#9XZii@c_KrZ#dG2I((511C zKg}9%pp+%>KEC?_dw@N%37FBCz1+2lrYQZm=*gJ1rm_Spl8G_)`D})B+4OS&vKsH| z_>cWhY_BS)<)itq=s7?J@5@+aX{+mxhuWrIgkPu&eiR3c_>V^d*fo?~^v zj&+yZ#uLVZ+f*p45Ee)XrMT^_C1zV22uUt1d0R}+6q ztfZL;9>z(;b7S=c1~o6or|mTJW9!Zk^a?=d!R@@W3%{LPaS z)o9{Dhcg8Ka@JCapiD*Y({)rlJs{^^>Bfq#%=cK&uDiEoyK5IU&JT8wUUMVa; ztO~1g^Osr8?UMQb41xW&k7pp^IxP=3-BC4;y!gK5%5F@^#nlnN@+v>v@b|ok&C+_` zmYbnk_7#){x{aLw`$Gf5#G`uwF1?jET)bt-s(Zj_)hut+j#{e5k;9j=SC>Y;U_;){X1X@JC_)BR zm1Ro>cOI&RP$oV)>i(u**>v&QE6y54Vw|LSn97;$ZEwJw*KS&q!4bmeqlw$(ZkK_7s?tr9lW}WK*y^~c5O$- z^4n&))RJhOdODer?U#xMNWd(&?#MAUg_?3>kM4ob8OwX_0GEW7?%EE3?jx@Aqn8z1 zIaD|K(D6j8$>tbW-5N+qBf>Dq6s%NPJ~S zVWCq?8m%FEhgRUcrl>XRHP(1qN#n!^SF7CPM=diBVpDIxmR2Vii&px&+n_w?85eIp z0inZv-8bEq64b1E0Wc9GSgcG#BS306$)^s;>j6Ze+cTU4>nC=71>2CHO>{H(@V6bQ zXySMn?&OVcZv*mqu7CPaFIpWaEEK;IH3$CsKRM_qO-{lk^S5#E+1EE1^dHjf>xg8s_{92Kz>B|3Q zV`VN4?c?v?WjL{%JQmVXmq!oeO@w)3M^hPX?hMZ>b62%MJ%o zM7@PJBwf`-NX$3@BGcJaORe@7vT9>Oe)!VZB1~CtGpw6fz_v+kTx()?A8k{%jegdx zKWT&o{*oCMso@864|vM&Z9#GkJqq{Sf`=gMsTug{8qOs`9oq|xX&V_Jpdf${4EuMw zgrU2A`pwNwxGM=L%UYPq;>QOm)S@#Fw4>KQ?Q(>Y*;8}uSn1sOhr!yy!rJ-1S4!=F zGL`BeS|e^Pygmjux=X5{dlVrmO`rSMifre*yR^>T_2|qf<^9+YI+qH#rGa&cUVe?k z-x-I+tv1nu8N%q?^6(2pH*#uKYGwWYkHNO&`~PJP-krF9DR?2Ai^`(V@E zzuOCoe!*|&cF1<0xiEwQUID5C<%htkXEMgwvBa8*$OVAEhwg33b3+yGz)n*iBpV#i zpSbMqB={8w<#|zY)Ym-q%yX$bY>bY|?K8mq$fV{OdQo#R2o>@u!0Pr78`c`;3K3I=Y_E$l=yfL;+1&AVhhq01DdL+U`4b_ZFJ}QnC3KWJ1(USp;Sj;?`fS zKsCpPjEB*yKWW3V*PO48zpEeQf)sRg1TAX9HoCw%&%mSW?on)NmX23V4+l1(=3;fc zL)e+n)Rm9#ro&W9ylDB@R6w@>LPf?Kh}u&iIYvgXTY?p)OFb}LtDSLKx2`y@321p-QlK{ zU&j+p0i}^u2|b;0$TP)F0YkcJ(M`M`3rMQtKlIEu8n2BJvW^E|czh}=b-2>YYkBJR9DGCY z+=U%7ggRxjM@fkuxY^}O{I)lm+mR6!JKsf~rgS476>nwcJdV(fPfmYt@U+g=9RYIU zypRw+*Xwap(FLBSM1tr+auXnN+(~T(_S}48t$D4K7mc9D^S^g^KspdM7p9@7t2;Ya zNTLGQz6}FHhybLMRi+kR+q{y*NuTTY>O>S9-L6YN*Y|58fc40&T}^VfZOJNW=t|FS-}cX^o=Do#nn#?- zLeKTwGq+UT1#$m_)4{#5oHWwKjywCYscx^XR`fa^VE1e_~`HOCMbCBYm3Sfooi z2YfB-h}&ON(}^B?48#39%V2QcJD@T_gOUMlabT(Z@mS%yKSi{hLM(|^=8TF75-R~o8%tr^lLZQ3e@dKUcWmhSrumjAQr z%g{O%@H73*MF&MIi-m6|hDpY#^WB?HC~22iHjFj)37@$q%jj#1eHbF_2_|*uRFcXudh-F23<+T#n@&hEl4~S}Z4`ZOcFf&$y;)iCVhxv^Vlp+vyZ?3|}A;J+?{9PC0nj%d2G)ZIjMo^wBW{o*%tYa?AK22E67kJLN zyRQE-J637KKQ&QLbJ1<{q(yuuD`DH5rHWQ6)Y@++syblAH3_D7c3OeDrV{-2Zf@w9 zg(g!qeC3abjrw8Twuy?hds2BynX>RU#Y3DV=a(!hmszFl*03YVm_$xxYER#`@Jn72Gu-$uh6{02V}92%pSvU} z!VH$P?fKI-6dY!)K@p$YS(bmA;9vRXjgZ&jYDX3EX6Z9Rl7t*0%ytzYiHYQuVDy&D zYnfGqr*vNB^5olR-x&@nH>_Pr4C!K0i*lm|_miknf*U26RiPY#&|@K~3}F=}i%*-Z zOe7p=TZ^D2ayvM)x@{(xJ~eQMrzOPMu{9OJoDsi^08OM)y+w1_1`}dos$Yjod$KmD ztkfuK5po2Q!Vs1|wdsh&czH0}-8pn2dm&Dc)=_UN9$<61lBkH;_3pU!pA5nl9SDn0 zl>({5ljSB9;iOuOhKyjX7!%%>XUm;CjEl2k#x5ldN3G%gl8skeZ81}Sn`h7NCmHD_ zHVfjj-7Pb;y2b2LvEO*ET4`lvVngw$ zgse9y@Zr(Ez`1e~{D&KGv+GxT=cR&SjGApv#e2&`n8tnFbz54$@&cV$OeU~n-rwtB z#?GdXXgs%5jh?CKsQ2424Vzo^N=X(%4~Sp7JB@OroHh1)(k@(bQ=NA( zbZQK0v9j~?P{;9l8$022bH@^KA`hgZZ1Q#lR;!|1SuBuT}PqN)d{i{93>v>cu zUNa5L>3IN{w8|QOz#4P3F~P41s-q*C2rap~xiT4Nle=>yQ+udV)slR5&A6{m2eB$E zi)m7wndL=>#Ff~fVr9$JcA{117{8ikV;=tPSt6@{PT{QhevxgPd>GT@Q%_e%n8FCl zDvU|Z<(-%v1BIPTAUXA6&O5?G#>fQMa}I*cqs4FLyGw}^&g2p?=6#H8av9url%1PB zyHl!y>Fg`H;FrF?k45WpwCPtKCEKuJ&1gj?vW@4M>|cMCDEAPOSnxD)zs|yv^0W0v z^CTI@A%|C(^RYb4`$w6XCA9=IySrtalm>RGP~W@oN11tQo8P#om;wAixzV$QvR>6>9LvEe&U z>%mI|>BzeP^FvMIvDLidHa)gOTwviyR8JauY0m;x^l{ahnL9s>oy4bJZ%P@ykQ1qs zc8GYYX7{>XJGh# zPqU?{UWIIzqFYmLT1!|GwQK73lSWI$gAM-1$LE{{2AL~$4dOAAgr}qiB5&6tG?u1s zN5i{FW;a47$zu9Bnn9inxw@TTMqYiQYMI(>sF9~RSo4O^BHylONLO9pIf&|;YKmQm zB7Br4-AJQGQ12*Ss) z>=V)g4%FDJ)D?=yBAM|fe#dcZMFe^8r<8#5rFRxLu3`ohB~;WSGOr;fPnIdF{IV7_$!fRZMbx{p5@XsPw}q?xqd*DJz8BH zNCvxr%o?-Kd&)l8U+G076QWj%V!t4RC?3WPthVFT+YsCG?E==Gf2WORgK!fCk%MV2^ z%x8`%L08FFJ`yJuX1g1*90VB;iyQgfLDp^858d-Kko{`6_QQ~I(Z)ct72^P`eolbB zgftHNMtBoMM_BPhYQ%wG*_6_H3Gzia|dmVeD#e&)G1^3F!Ul&pvmt z|9#8zfAedNqu*f=y1PlHrXaRBFsDP=6UKbTKe*6}{GlZd5?@A~gb#^o&2OgDjkcil>}!rl zRejx=d6;HvW1Nl%FUu96WxWMW>ozEeWjtJSw5%s8~lKRW@kevSS5|t61>ai;&c5blL84=Kuzy`46GP-jz z`>YKQf#wuYGjt&d5mbz$R=etgr#WK;+Rjoo_z+QuG3e41`3P6;viq3#X~gRI&iv|( zpR2&>Oe5;k8&A0%W^zu=(Gh{z*)o)(D*_DgYkyX8Xo<)&}amIllmej9LCFy{1dMf+aez>@dp9@>|mT4;C=MpC( ze0+VK0;1pC*v+5EQGg|JGLiN^e8i`)n$MBCEc5BhO=g-|CNYt~H--=mO!3^MAH~Zl zafn=i2!7^*hl!bV2pWUc;DY17DU+kTQS~p?}W z#N`UgTOA}Jn{d#zg?w6^HOV9<+mr&@gJ+uxu`6nVn|}qKc24C zGapbpN)cgfm^c}u*(TxPOHEhqDcG3=f*KeoUIz;$Vqgd?Xl^D&sE!R8a!W+~?xzip z?P?41biAw|TjkiVRiGM=4!_*g^9z8wJux~bpvFghITIhgKXS~k9A5{1P0rzL)g$Ly ze6K7!NywtPdx@$Y&J&yra*FJq`H=dw^h7Zr)jj@n8NR{ZqzrVcZZ%5y=sO*nQJ-)* z%PT(%jw&z50P-J-J%|cYmX)x zevi@%mzF-iV-P$h$OJ7c2bPdAIw=ma0G@2a_Z^x}b zLdY#qrREB5Ys;Ja_k0GN??^w(2E|Z+vgxB$P^4}6LUbBL?bM8-ITIAp_fZXCO_9Z#-AfPYoNaYWN>g;K#!PF!tQ!}lDJLZ*S*B3hElDJ%3g$mbD|qxd zgqeAM?vPAcg_&%$EycGr6R|9s*?c&D(ZRWYdA+)&J0U(k{vMbVbib{8jDhfBR*D>~ z3xY1)lOep_e-9CXx3>Gl=Llar{wM#~(>2EMJ0-Ahldz&$skIufwN+o@|M|CF}nqq5m-wI)2nh0znD7IyyCc-`F|$=05jWRU*T6)SJhJ78g4YEg(~S zU`Zhczxw({M5K8K4x^IoH}XpqRA9X2%?k3CMia-hQ~syQ4VWby#U*kV4OK~1HwC0X zFM-{gV`P_L?$CNduz%o&t4HewpLSm$9Q~ldd9_ouJ+m5 z0K#PO)xCl6gEA9D&2);yV_9bgn?`GnWA)!{LB zzxiN6;6+SH2uqt7rLC8vib*MW-=hQ@EFgeSvpF+6#_2jWIz{wZ>C`FJ>hFPhcy94Jk22OpCmTz8uv%DT zV4`hd0@aCtc-zL?b7a@DxPPQh)s3zVz=s+ss0@4)nT}&o_6c}eLG%(omy;COK{vvG zgV}3K+-7Y0Gt9i?WP>U?>#Sx_P=I1RIVB~p4O;CO+pQ9Ei%ks^8r&dp{{10tes5xF zRXZvoQ*(Gf*g4fixBx{xZ|wtz*AZ@b_0w4?HC_-DY-WH zWTQ6LMh|Dg0wq8Dx3CQV15QCfMfh;TGtKj4+HPQB!l#W{sL!yENj=!Qwg_kY&#LKK zd+*{?A9^LQNn=9;0-7#vxbrL}Fb3Jd1q3U#8m&BeGk4>}YMnJrvzEKgG*z3n8>|9^ zOo~JtMu5iK_H3U6@TPiYdBnOvfuZ@)2`UVeI(qzU?+Bxg!lS!~eB8TML- zY$m~m&{_V2c0M0+3z_;fUJkORD6s|NR~y6H-z1!BExi#j)|$}aWr1z=eAIM670!NA z(0}Cb_iFpY@}g^Op-KrYJa-WzOnY)0(&8aFjP9t`k2bzWd7((fKH+k2)oQ1K;cc9* zN}_$+9|k1&ud;^>ghG!rTv5HQp_sSF5;BMhwJ(4AZG-YWbM3@GUc;ILHVy zvu>aXjQjZbXFxbL48sE3@B(AW%}J$u4M)@>mGgR5jp>8@8y;3o}`?qdqImvs$v zCXK(?HEW)jM%Q{f9sT;%$5ms<1NHw~ZCDW6vsUY_7@hxPLQLqkwGEHKVqUz*Jvay> z-a0-#<}5Y~gwO(pWSJ#1;W zR6aGCF@oQ4KPi?v`jhr#B3@HN$YF0NQjw$Kb6j(?0M7OJbWx>;Q4060L6=b~v>=4*{dT8({VZC6*v0bT$Cd{J}QSlZ~3 zVbln!t{-Hmyi1JvM4QWEwuMJLl`~%UPxH zR{x=GF^D6WgI6L2o>BQ>F|A137cEl4d`Itl-??CYAiBQ?L0u~~+(riQVpXk8+CO={8&M`&l+ZT* zU-p^uloI;?CAkhrpCW=ntQ7@(ZDBH&&uTAxcukzk9Q+e4?rm;WlK?MWNq87Y=c6Zr zU@dZs8LMX?g>L*y{^y4o)5n&Fhe)9H`46#K`J|}dwXg0d2;KIdHR9|l_UC6`txvdU zmzij`%t#!PhMX8LM6k<1ki*)SpSj6g=t2BzB&TA&K=oV^y=3CGvS5=)?T~nU3Re`s ze0jOXNi64ekt`-A6H?p#eSJC_ZjmgUAo>#FaKesEAyMvbA*STlp*E|_r$?)ONJ{EO z%@(Sv6oFOg$Ef{A6ISdkrksW)9=iSC4SVk|Wr$rj*z^Ly6IBpoge|wo+w?!vgAKyV zq(+oQ3{5s`cEthqb3B=3gZW|W*@bM^InrvVpiV-xaJV}tA|s+>U%YEPEo|>z>HMi- zm>dlcy;a?=)GbJj&z3DdJXE+`(dUQz;Dqj*BB8-8*8z&Ej`X(xjaKh8raW`P zZ*`g295+P6b2UQF>j^F7I8G~(H)j zjZeH6Q!k5~=<%A=s%Ma1)<(RNt#&oAs5o^3h;Q>Yw#u$%wUCtIWNnRZQ>`N=aFJ=m zn1OV%gmWtx>ApPwYdBU`F1GN{h^lC)Eqg7fJ8=AqyLZYsQOKFZu01^zGR2zGwC8C0 zwxj>l(v(_RS!{WbTc^uJX&CrWR@ggOa`C3QaN2%*+SckutW@MBt-l@=}VB|E5efES&0%U%kDcWaU2!`4D-9#vA(j0*B^o?j+^6iO;FMlJMiuR4*(&E#aM1^hkp^heel-=V=y3 zh+cM#WTYPabbx70jSE$KeWhtY+WNc-w9_g=ZZb?@05swh6+*d8Z1txWxt1vTy6x?nz14}b zn-o+4lbjUTCBh>jQ+ll=PsfAz``(Mf3HirhzloBFhr=_|77Lx7oo(@G8&rz!meJc?SPKH9cK1l%g9rERG^-Q}fE9{}@*5qQf|ID4dJcJ_L z&vT0=e{+Vx zyvGaEEUc57;-h{^7mDIuzO0|1bL);y-S4v}WFX`m}aiT>L9x!P-9CnLJ~6UfvP8mG)^r*c7FOi71* z4~h{h;deT!_b|8DM*iB-?Tt$&2ulp(n^K#e9Q`@R!COiT$)sFTQPfe3nMYG!`1QbE z&F@P`zrA7(6D=3)*N>iIP5xF(z3ZQwoS1yx1mRvb5R(vlJXnR3PM)fk;LX#N$Gtp5 z$=Y1&+#tyETDtkjefDC~z?sFT=f&^TjS}hrNXMd^@Z7DNyr?(kY&Q$~64(!(G)tc!#x!!@m4IS8Q6#_{Eo<=o(5*Q>m?Upa zr&m^raUVTiXT8rXBi$3A3QRSgB@jE$Z#P_+<#bETF!p^~ZbW8)F?!msmDaQ2alfjs zZUqfRMAVrvAU?wfw7kKZu@cNc`!M3 zn+GVQr&=VRJsx0qNabAI+uAb(d<_dgMnI+Qd&7BCu~Ypgc3Yp0#v8{6@ZsOE@VE^Ee=kQyhUQUX;ZCDr0QCK5|| zVIvVi9+YO+gnqI0T4-dgnT@a8&)5l13G&%Ho;^4gpUWySe}K3FU>|&#r#KE7s>SDp zKZ<%6Ddjr2jRQpmTM$IVK#}2EJX~Ao;Zp!bbr3}p1#*akFrCDf^rZa22ppURRb-^- zX^-alvN5#$(sOCj7Y47btsR%E;=x6DUJoVHvhmC?FpGwWw3yG4k9OPr5e*wL0FEi+79Qb!@&;Qlr;ruuoh1W*e#_E#-n_ zj(H5&d5zd%$drU!>ay0-WEvhdHNP?Gz0<{omE(nr`gFlco;?!25RfCQp&oEI-876l zN7hzrsN-Evi=JDOH7}nPz7EANb|X(ah&QD1$@zwnp1(qE>loWfX7xWlrPN|p`W&vT zcY$hzh3uRSEaI|0-PLR(GqcE2ZAF?luUTN(oSYP~(I}y=m_k*p1gFV*&5h)<&*yig zw7a~n9&AWY63-EpzvfapN8YARex>~Oo4IVBX$c=Sxm1zC;#JL!L8(cyIU){XZBS5m z>WrCt41XRLfk9Z>D>R=7NL&OnH!`$^p!s!rMwY_VtgOuf6O+mo+;r*kD-6*2(&!>e z$n-r)yKJVb+1!cClA)!Q#;d~5cfe1v+?OFW0W#nv>@@QtQ%Onf=5~ZGi?gT40s)eh zia|1e>FF7F-rJ7K4@sX)&Mk+zUtxp}zb2AF9I5f`Z&;?(TU%7x(a>hKHu@?DNI*Y)(4to(q)0#EIVWw6&n0V z!BcP7aS?s`?EZI~S$z<79n7%*YvKinI)(1vQ#WI_A)lM2Do{beB+I|2`UA0KMsVMN zxbBKvq`JmxG<9{3Xj{oJ4^kVbGxPZ&3)4M(>;p;A=yE|?EKWeK3Jnbx!9Zhm`d}cJ zT@WYWrKQo}xl&B_&Ov`WX~cZCKPjsY*EkwX1>>>8j@Kd64-uhcoVB@_$|7CcuFYmb zW;{0aSSpn4)5~&N<`z88pVz2iIG>$Xz=9lv&h)Qu+TUoE=VHK~XG}4i0dc9hx-1fh zu)4_Tomv{9=dV~`G-yv9sQ#aJvAcygnDt3B-E zg)7jk4I^R0FJ45qrMU0+%->tU*zacvj^3UlD)?9zNz^u^74lEaW8_paot=r7pv>IY zp%D@Z9-f!MgewwP?G6qPYnxhp{=Td@{odILgA3Z1M6ezL9vrso0KUGnQxy&TxAW90EvQyvxH~V%RITe&EK>9W$1QK!_*Mjfu zqfNN)b(}<7%(aI_#`g18&|LlcQW`R)p?>&pf6ajkEO727N}9GoFbyrOgw6V8#tcdS znzn;yQ8#VBy}eq`{&zWQe+d|FZSrj_ku=TmXhLV@x+`h=k*fQ{!MY>rV$y1N0SsN2e`k zPddXJb6@XrJy0j{cF^7Ht4{E!egIVH^}lqiJ0Psq4yCZwe3)s;_Y^?GgY~f-lh6gJy+`OM%Fk4US25Mi78IMSaQRdr2cl%1li5G3Z)tcPNDWDr00~ z>;G2J+GjqH8eGHV@9OHRg1mgWoCyRyyCZSy2@JghtoHurZ$-GX<6S>YN$qfvF8_Hl zGHzyO88C_XpB5VP-=l>bM(^_)H@&c`+&8-lg}B|7xb+-{{sMf$edMU<6+x?>-$Sa> zB?$=$*XY(J>noS~GKy~U>L-+v^}K@U!$q$+z1>!`OYq>81X8s!tbUJ4;M9ERw`Zg1 zv%RyUQEAmXT|k*ZhgicDj+APvINr|1k>f8P2Q%D^LwRI{Yux$et-wny7@ zF&Ih6hpSz-UTqL{3kd#8xgU+s=|-)ka2ho|eKEKk(@A5Zl_BOyv0*Kc;(^^q+wZTT zXhq#}Sym5cg3;i)8x!`>wnb=!TVqa8V8$cH9EQe2u|{sbC}1GP6jK-6+}x@Hj@@8` zp%<$iX5>|yx)X|o-=Cl zQHOhgU7&UPF*9RALUuWU<(Zt^M1en~_Ua0wLnOR(q%c379fJPD1-Qxx4xX~bpV-+U z7d_?WpmVMA1EX;e0%l=bxAJw98riQ_3^p8tw%>bB_^%LadB~x(b1^I?qq6OT4Z_o zeNW7YvQeW~VzTaSX9)>{fv{gudG6zayc)czQ7KNkdR&o{K1JOKA|G`~(PcBYTg2yP z@1G-Qzbo;`NbBWi(#_G*l??ku3}$|PbM!p$_RNN!gnG$yaw@*BLy8U87@f)6w+ud~J2oy@O0l&_4cvek4P`_j4g z>LLxF7P{03m;{eBD4(wl!=!OXIY#0aA*ZnISvLY|VUOvu0gA5a_KldcgpPE*L?VJ&cgX5SUV(10mzd}u$y)97^z0yyd`t8bbT?O1u7 z)XBP{wRa~QaOmmTqmb+2ls64mcWQim&OnYFH#@r=TRf~IoO0+#+&%4PKTp_To$GwU zhYzMR#v~!!_ay2bs&f8Y36IVeO9T~D63hPjSmp1UA3x+705XF=bp7+RH%&y9;H1W@ z?H57aQZbp&7K9_ObpfdEe*2s5qNQbo_!=5rT-NICrTYsYo3<2+DP;{!&BTNR7EbR6 z_1xHVBAlh zJOMv?|8NHGNP=&Xc~AQyh>3|Q=;Pt=mt)fv=5qcAw;_N}XRjJI|FaA3FMi4Oi%A9^ zAebur3Pf&CdC#nZeTC=#{;h^Rox$^Y^u$Wz3KkYDeEt0mkPnDLhV@hgo=K>Ti!^C0RpLAG+xpH&1!{` z#m+~v8eO1*3{yC}WPavG(eVG8nQ4^?V@ngQ>_2cME=Nw3dN{%a45mhezA2_noQl`U zIRSI7@WWQX%#DMdds8vau9pHmPbelFnos{`V1M{?v;ye^_S3-*;r@aH^Fs9J#d&juiPRo7N7c65k8UdIXx(?xarC8VCTwCLd9Row=@+1qk|^AP|}X&JLoT;6p_!c!| zX;xB+`g)j8S`~;$v#?0s2Uo9O&&g9v<`NUrzi{Egub)3TYbu)k-I)&iK0c7;)+sJ; zG65b!IYpq8bm}}rcUR&Qc@59MVQiH|pXI)`9m-#=U(N*eJ+FFI;780E#Jze>z6+lI znv#Od8#WZsNi3BpMEH~tGxL&*}D|1Vr<`A-i`X*@F;Yf@2-!_&?FLT2sf z(!e5JL_^7LeQ3?K*8^(e!&T&s%nCYzSyk%caJW=a_k2S5IXXJJhR00Qma*9OrHh`Q z#_#q^Gn~j0$;6RUafE&O=K!`Xbh3UjK5~(Pdn{*o+X4I=PPe=`>ucKM75-&nHBt9HE`g|nsPyYnP*8xg2-yyP%g!Kz8t+Jay0oLv5Og-C z8-Bnp0Z>%&bTz2`$B=qk5Tfv&WPtIuThh*AYR$RRnmTEJg+`KJf{O{=X$66o~|NFR&>l zeS1eXJfW~6TQ(x8NDwj=3?&;?!@)~|L7Dny5uW{rkF;&~3Zp5R?!V<16ckInl2=jD zv9hvCp`)arfM;c8v8)c}DV}Gu)J19)_G}Q#yd$S7k3VBID!brJ0Jdg&&1sYjVp;&N za_#nKt98wn1n-0buFEbpxAu0#~H{>+IOS6K{80Ls%!mRc$oP+8Z+DYh5Ejo&- z%p9HbQh>YW;wvJ0`TNsjyU}tpFgO(z$c^?#3{VZ8_ArfDM#*23Y|xE&+Ys4(>*Lj( z%(NS3NbNr@1P$*YP$vKT)Px#VYqUBJx2S0-v45K^Tdn9AmP#fdS#5{8YSm6SvW{5m+=&h}Emp!4` z+k~~d+uDxPGL=PNxG?Vfz$lhAR?|SU`C2SMk_GY#m#zTYNCg>G62D0)h=QHpzbpJQ z8p?K^|GqZe+_>3_4wV!uXQWy@edz7K1qLchqXKe_P zH9h5%bEbKw;6TX!K683q+fNsD-&=@R<>KTl{1#r?e4rn=kJ3HQb^mrdi(Q-9WY9IC zIikf6w!-$_FP+pzeK=wE{ppEiS!I4+Ezi*h>&Q$av6WQelH3~=eIyWiX#qC;OzPD~ zVaL==-=!4dEg`#Mc`!TOJcYy?G&G2g2r6z?)@Sf4*ZJoltZUMVIBC6j@zF?}@)I$z z?t~pGx$zG^>WzGcYV%^%s7ilp%FSsQesx+en(hXDrbUYdn*=0t0qCJL{!H^ao6-lC z^XJa3tRk{TK-fVQsOJ!$65vU1s*oR8M>QBGLn=@o42w<`vR|KZhp0h4hxCRJ{pvWK zzpYzs%-X;ztv1CajvXJcH@D1%WRx<*w1Js};5-OI&TzB8cM+Mtzkg9M9=8TeoiT7v zy%)qRdvqy^Dfmo%;Q^$^6-xF(ecdJZYoO2$KH{FQ@9onNRUc3mqB#E+u)KH;YLwu6 z>y`bq6vQB(Jz2?#t#TL>07?LMS{yD((pB%u1)=)(T#!O-!1zMJRRP>LSVMz6yaLNYKPftK( zmhitUClI*}>c6@iGUrH1kpSC{*E(lS`)^53`OHU$(~ehK$Lf?Ca>CX|D{>tDFMN8* zEaAfs z7}g;PW!adhRTY_ZdDE9EXM%O`f{@9A)Ils5*93s5cD0>0fH;Nzhns}tsHwTRdxRuY2D&`2p$aldK1A-E zJfOa*=bU(EO!aDjEO}eLR4nwT8Tplvjyk0ffcVdOGyK=W+)#IvKm{h046&W?pPgq ziVw;)&kbrE299@^2{0Y(ASVZh{M_7}*D4PrBJ0C(J^WrGs9QqxU|zQbt=`X(3J3`7 z{bbKt0l6G1Q_`Q1c%uq6yQ~<_&E}I+h2N(y3;+s0>$Aw%4vHxVfZjD8KYr}DHz4OZ z6L@F~z_LJ!Qq2kz6Yotv;;;o{CMIP z((%aRj%d3qQD)1Fv`XN%wQMrKd`Jg%lEcj@&%G6OLZgv zh)aoxsD9=%fY$9vy~HkLpkQ{N{^wu9c~IZ;anaC#tE4~52C1g~ z*cVHlo80&DKUr(%4ipi|L%326U8(IS{=qul!2flzI25bSm~!o0S?2MP-;c zWSGurYYwenQkIVy+8&PDsqo583nM&;o$w&GB8&`=QG?cl2M<^o>91Y;4BSu=K+TpW zfvKL%P#S^LU?|TJsLok+G^_TyNO0&O``@b4KBd!BWf5|!PD;6k8h`eEt4HwedlJm8 zw@rG{1Y(E@B8jmMbJN>%?HDjtKj)!v-4#J z;85R;Njj~HCc%6pnMM`USH+S{Nd+v#%W>KM#w{Q~@3|k_E+>Y?#u*23EQNp>LVLRM z#?p@L09D*2gZ1Qjjs{_m?q;8-mv{P7HhUIY_u@?$+)ZW6)6IES3n&gfv^=p$_X&rh z#k@}T{wYqE@N#ExN}JARd?cE7nRxEioP1{QfGXoPKDO1N`WZzy>E4TYGlDyXYup@QW$(L0&DT_q`XF}X-OZ&;o&y|hD zbq|Tp%@PSHu}x`Kzv}ZH+k*E!NZI?{ipz`UZ_f4Vik;YyVAYm;`3P>kvj@~RGZc&) zfR<3PDPJg^E8lf=98r?? zTK(KuV#_wFs|pa z_`x}CkbZ-H;{l_ZzF))I2(pl<*}nkrjY)$^Ld*gGRzyUyAXP&-1}h`9I_>WX)M1Bf zj`LhcoEp71ITifj-)QgZomyq2UUO>S22Ob^@E|cWYo~NjkmBGpuNHv;r`f;RHBI8m zwr226R@P~}@rd2CDvv}kDzeQV)NGbC)x-K)aj}lHr-NxH?lX^zu=cbgYJ$_iHTZc| zHej)O?)!h!ompWtR1Tn3iu>U~o=76xZ|sXIqUatE(M!OVUn!xoQh>)e8{`3o^-5v# z%-~{G74b&P&J5MVy^&WZ>(XUhxo+hscg1?9$gY;N{1*LkP^GT_eW37)D0TaYy!mLD z!`DCA@iLl9QO9nlii&WIA9p$o4nw!F{-47qHGG2LFgM z*}CA3&rgec9;AP77SO&Jq<6uFt$S(XrWcFXQ5yd->{+ zUG-Frz+Xo*H5n~bgyaB6B4U;Fki9&zqu;;WSQ0ypO!V70ZM-B1q)fYcjj{CMB35<( z&+s8Zy#X>VaN0-}1BzR-5eXl27+Idd6>#18>Sw*EF8u=l40+^SiQ_>0-rTkTEFRy4i z;HB{g?9o{g+VEkCrV3h-0t7fqerB9Z;7RZ|OB%JbNYVZ-ifE@DL5Cfk1JpSTkYq|^hw~zS-;i>xilB8#rsgp`ei>L=j9OQsFD?hlFZ4{d2W@z+3N1{ zYCbJYNCOCfA+wZw3MJ0fo<$qjncNyr4>uMo9+>X;j(p zQY8y@B3;8R%Bp>R#z(G8HFHGHxy7Oy4|ps?&9Tz<7hW(-JSzYuWNBe#xAC1S#FC#m6oJfEjs=lBgetvOXUtf z&%IW3Cc1pWNdpL$!qKG0>kZ=(+C5USu7KUx#|S7<&abbJlQyW90K7*pFCD@`)qmDNOzHpI!gx87L5}x7`D5;e)jN9%A zs;>N`L2z($8${_8qX07{2@^2S-dY0agXfPST^M{{ksuB55Ehea2}SC4t`+t^>HFa)liN0MeNRES3^GD2ylddW43yK{~!zJ zXq#z0GU=oKeyO!}bc5t{Z+Ma_-|jayCjrEs8D4#)+*V_w)^gpT9N_(f9tIZJka3Es z(9v+Aw(P~$BN@PN%>OuKy+7`swX*q@ghIm7_i%`xmuR!KOu~R!LLLqX{kYSeO0jE! zPe30Gr*0OJ!5x`LFSL+)25>-SxXK=6dKhDqlNZ&J6r0}oQF1auJR(AZKi0ok4UiZW z@zZ`got$v3g@E%p2Mi~Q82l*UQ(BBNfTo>HOoNwJKOaMt8Ab(rgh9R}`8H2AW56MC zV|g>pna~~xW`uo!_m{=nAqHwM@4P{>-GrOzDSPOT>+jqopq$)Ec*XMkwaDVg;at}@8vFPDAgwx`0HygO z0rn&&mVoxD8>2~wcbY*wVoiF`i%+`;Q zPl-gp?UvAMJ_O16qIfd$jh#i`xC{{TwYu04`8Vr%3*h@^PoEWPjGaur zl>p@sBz*#1DxdL#tK!av!8TWSxp#_go_Gt@hYAGLWeIJOMF)6&$%~9@&B~%3E-FE4 zKe<7B?d6!W;(`-slv1xrbo-JQ*BT2L^fU%i2MWjC%WTFcr`|fl5(#HW9oGPqWE(BO zx_ts)GW<@ER?C@8rM-YXle5V5OJKkA?|G?n$+>`3|ICTovFT?I7cT8=GSE%2=j4?7 z(u*KGe!1ydRp(Fqw@(F~(~&#qid&L@&oBaMy>@D~HBte)tdwi+8K`=$4W~L;&_2bb zeLCXfkH_LyvEG+!c<$RgT|RK+DcjX`5IFGXI@Twq2mIB#$^{QZMgvmR0CnLC2k~5X zz)qRhWRZb2Ai4H&VjsD0zAm++HC{LXQ8~$ZxXduS9+1xfPOdE`H=3lq&)%P2*~Dj; zxBR&WYgS>Y-s;PQ*OhGa;BcEc>xlT3V^!U%*cmQj_bHC*5~rs4aqx&>#j@fgey<*W zNCYM@_v9bD5l0l}y3B7fG646kjEYHFq?@M%)#GwLJlV?n`kxN(WczWjant2vwnQ&? z`=n8ga?5vXo!?EG7gi0oD0dT20IJzAuA}~9<;HZd{Kh?fefuZyDq91KSMv(yTiN&E zZn{-`d=m7a#m;>>it0)}KKOZjhSAv-#N`eBZV*ey#>POjt~5?l^P#+S?{2A`m8~`C zHS65;7v=3`Q&oiwm}TldfQDp8CzFP8bit^K4}x3C?Uzz0?)EcXzsH@=d^q&g@?^R( zAX0a(b5gFLWD|)=68uD!vjUDYGEbIeJ5^?DlIx{;Mk-?^H7pXd$7Zk=zEk)U#`87% zTPWGxAe=UQ@3@JHH>%o*6P!#|E@9PX>jr<&oy5nb6jtr+-9>*mTIk+VaNsT*c7O#- zXWvOgHTqkZOqc7W)ocysZ!`|)Xr5~9i*E%?i5r(t8hG1HF&TKCE!YY>KJ`6CNuyd6dl25@}4uVkpUoTvZ@XI7ozZPineAiRNUt{kVX*nFy<8ZMNO(D-`>Bwq$o)>x*A zy*`9HVWA4ByoDPZl0LbkfhGZVRhENiSXCvC3O_B-5t~#t@nQu#Ks9APh8z7B%*|c2 z4VW5$c|K@>=dQqq}{(+tSU0L(96!j-=` zlsYJ;_1;7{)9_W>1_Mkv2wEjkt!&Mu*oauJdxDzmDj5T5L(&AuqQ4{@aDTD$NtX`A zrQZ>Fd=P+Vq|+iAn$vhKxtI(!(D(N2`3Ab+Lj3-^Jw7pku3XA*H&5fxb<|NrPwI#C z-*yZk`V*Q&0j^+(kt)^2oxTE3PJ;{s$ErJ-N<8c zOj=70)wrXWeZ}$VXYK5=+*~bx*%UZDNsxg_LRt}8zSGJQWGInFq8j8$MqgY`k z!T9*n=A71`6UR3K0)mMj(?5rq@C{i?ZVe7x|9en=?h&ox#}KZ~n)epX>vTwlP3p?C zUhTn)wv#_1f&mL@s}t{UI8$nz16lzv$)0rk-Jr_oi7LLpKPnC3(T>ffXfU*&+ul<^ zv9;k~~ja+Ssa zBq*Wc=Gm1d8vZue4`aqv^uN4J1kIeK7X+ud)ml6@)6O$42|FEKIrj0l-?JA?NPr_B zXj!!m1$9uX4} z7yOxI>6Mqt=et9x)sWE}Wi8`(S46F&6d6J0%+GH$DX?XLW22SZtfjJrgyRlVzvDwinhR?yuXIr(#Bq&8z`ditQZ z*KZdacCuPasnE}HbV{Snt%de(cp_pnqlc!QlvZn1$;C?E-2Y~g(-2&Iq|_gi@mV#A zPj`H^i8a2ow6vkg$Ud4*>f6qDOBn4Wp3&1GxZ=A~pdxqBSzenMuKa`P{^p2YPPo*| zdaOg#M>dMRLy@Vgc7J{#ZcOB68~e^PV5{*V*#73`CJ)(U?)^c$WcDZ<<^ES|#f3kA zUY^KE>gwtSJ~*Z1)vHh->@zl)Nkc;ciOpYw@UN*>++AG<3bIK&yD5$K*Lp-x{7|Z5 zUVkiJncJQCU?LUA3%qOj@>Lf~scDO6k^_rr*jo!lnF6yzzp?o1O+=;IA6+N$a6jbp z3mSZQI>S0E&nKMrChMuEN29E&$$*%{uR&+d?@YVnr|#E<`ets;s73Eown&)hmsASw zyFW<}aG<`btv05;zpX}X3986IvlQf&;JVSY6jT>Ex3AQExgRijBRUIL%Cg$lu(du@ zm-Vx6B+i^YA=OKNf9>f_43R57apw6;x5)MNg#Z0oc7wat!Iii-Ix6sRDq3%s6|2;fhj30?JlC9B#`P5vqM zR-Bi{#FV{Fw)e4;@YQPLT>{}HQW{>;slo_OB9$dgmZk;%vyiPS|XjdM7P>)h?= zxN)0N$7v(^@OWx!>W0zG5yb^Hz5A&osL-g~v7d4t{-WzKH!|W&oUdOlU=&f2C7fJ) zyW_5<6AP`^OC3d7SxLOEG}l7vXhAUOku=D*;2GT5yeZy&y5t(x^xvZ5hK zrRKzvW%b<<;s)H{=+$!IkZf*<@Mfi8iwEa>^IRX}G31$)Tq)&>!Dp72Shj*K{j#daDI<+=9_cAkuM zCA-nd)5Cj~8Qd!a7~XBtQigkpxXFr%@}8+FqwKG*>`)Rq$u-ULLK2ePb>554-%l^@ zpMIC79^y!a=5j$rKI9Lx41|Ai)_Zv3u`2S6k>=*LVgysYiK=8k>*VCNS z4yh~NK4A3tJUHlu6kbC_w?0Z*zUQ-_K(=LdvZoI&)Gpbzzdy979NYT~FGFpl#x!iz z&@l{mf2m$+Ml(#Ee+0PmkeWshvCM<8uydHiSZC+hV;Qw$NgtNsj#wP zeq9EU@J(Kr*+K^%b8fQj7+{VTA^Wy{0d8P!Z?5DX&tBblfgLLpjlA)GX`^_ z!l2FlaP-UO_Y(1hw6vm6fOYJkhTf~|DdusUsFu^1-a)?A&(7}(Y&_9*9B;;Ws?aXAK&?3q|x*UA&kh+t_ejs5iAGzxx1zH?{q+P`gS4S`r+B{=6qFco1kR zE6|n%Pqqo%4CCha*5~tx)}m$O6fT(E=%-SuJ2VXE4 zlG`w-)pAh_0RkFM1-T$RH6QPPti2)Wq9)iSZ8A%LIOP>TQaqEp_g*RR1SR6P9b>?A zD!Dbp3eVNg{<;>_o5&jUzgqk9a46UC{}+`Oa?VMLEG3kkWXaxXJN6RULY5)R7}Ssv zrkn_;lw>WmVC=GnEG49|W#7ig&M;&wF_!QB`d+{5`u+3!@7Hy?H1GS&{XXyW-1~Fi zA5>orC4C`j8!;VkFRyE$$|W;x1Oy_D+7gzJ;lsmF!RcepJi^(D1lTSLaDfO=AHzOZf`Un{25{_O;p+u*{9-2m87x)RjL zRN%JOnzwlnD^~5YU2h%5y0Lk;W4Y%a$Z2hI%{@`O-M?CveBUJ~Ac&e6z8uIv*=$_A zP(;T@(G~SMfR4x|=#R|Px=-HPa%y3kvmX=?|KS%-n+m(<+@s~*U!UKw zo;b82rK2!0HaE--N_27$r<_JhYiZh^)oLr_p(iat^o=a-waoy3y>;iXcIODrfp1OU z+fV7m=)tl3L-kQJNb;4S% z-;`NjwRyHZbmZX|R4fX<{v}qUoy>AUnyjIre%5W|KpOSFp7CmnfB_kL7>is640YOz z&8(fMb&XAjV~aji+mucl6{|ZtziBZmGNXK32#TRe{g_G@lw5j4_@zvh6c;s(YN3@K zuFf8~9Ym?HIzo29mA%PI3UrAQacN#R5Y;sEx)>5Ft|F(M9u=jymQxmvZNC~oxM_wJ zoH2{pnH0dK=pJvQ8foOsy>}K2(3Vb6l-Fo!ZZ^2KJQEj#qgM7*ak~uUY7l$YAeGf=9JL|4n!^^Ruw1;U=X14{nhx5RD?8*|jmMyBF81P9tQcvITL-kM* zHht6eqSsMuGJ%R!Mz!2OQ4@jdm zOM*iDM^mCZi&*Civ!C#(UIn$9AUA3aL4d>wc^x6yHB0m-3iV3Smj*sbzv&yCxue_{ z|0W_FMU|vSU@m7q(T*HG(;{8{hHN_1yES58N_k^I{yT`LTeS$!9cYAgrSVX zlir;t|K#o2*$?b`6sIkP6YYlSG{$yLTV=o^9UsSZ8Cpzb_^n*1x}$YpNJ9`uJ^cM2 zpB0O`Q)5`uWA^!VIayf|n<|p&9SY%HrAUz0@?4iefIt34wC}sM5nNSIr|-mcZ`h0e z0*BJe2Cve1M=$eaRc1oB6>=Ka#7AT+`tQl}^eaQ9>sr|sW@6ZtIeg`&AN$%HyUht~ ze~DYx7EkTIaX8vh48I-M=_ zY4l7V*W`>hhMh?RH{Z^0b~ch{(<;v)^zZRy+C|-{m3o0raGcZT?ic;0MfnsGAw;@s zSNMsNxmcQ#(}D_1_?KB0m2Zzfa}CTt`smx{Stp?G?)RzL301fG>SEjRf0UJ#5pX{z zEq`N%Uc76ageR6Sjofg(^K*C9`@>qfKfuuN36{>pQWcrW#A}jg%}bZdtJ>t(tlE=w zb-N$RR-JKork{!^b{}``*y30B)(55T*f$9x!!+f{5;f~$L6zO}xbp~m8H87yK_e%0 z+8zhn zXZ)k9(^c>?t7~~>YsETcee+Ou=1K6bQ{H+VQXJNX*ft7$5SxTE*TWjcQ46Y}<&rS{ z>!;m`n7P+X{l^>3KU!A%xVG^+v;)Pr>P0AwuZ7=L@9a1gzdESWcV9(BMA-QMhtwxXK+~5TfBA%3cfozi?J`CZU$+QbDU(EdJXM?@??<4d>IGo0r zH&lH5IQ1x9uuDi%v*UFoPHxb(6HHC4&7kg~%bFQowoQ@+%}kGqA6uHJt**dLTVy+H zb?&&`c+)(ef2ZXG2sY`>l1HXjj#UhlhBcejc3h|APLph7W!lcTG#%C)xvGRz-;mj>37NP_Z z6bDHv&KgOHLSqq$LrzB#(pOaUzK2-sK_tX?hT5mG5Op7;L$l+sFmy-$6`c4H(#4&j zPc7IHiGAo$iCYsebcd=$TmVw@8NHwRFYjK2f9LzIg8~3$g2`fFxlW%GHHa;B%jvNd zw3(RW;$L~&jT=g98`hS#UGRx7o!8&_>SKgOrMH>uVE4qFnjWP^?S)OdqFGVT1hxV_ z*jvbdc4o@j4nhu>+8As&2bW~*%4=w(L9Uwqc^qL^yFdGJML@-5#>ddvG5S32>YqC_ z0rXU*x3YAIWEg90Z|)6k=gX6N z8Me~|55eANL7H!#&+fUjxV1uZ}5g2_Ph zt%-uHL6VE@m(DyKj_lCm(VHC^E7g;CObONGQ2c&vQ}uaxuC19yxlO8|b#4sKAek}K z0P8rhDn*pi6;`InWQF?7rpO7mWKtMmvfdr-Yg0;fETqrfg^F-v8J^&Twe*EQu z23t|6lUCtDe*+LbjRR!8Q-(?H{aQ+HKMHU!6BDi8mfX2>N6~%Ou~PNUQ<3RRy@Bb7 zKHO;#jV9_9Q*CsauD~)isxQv!tt_-b@Vb6`B5QG#A??}Zut{$#`fzfIJRiheLCIEd zzDrG+L}Of09WZhogUa<8A?eP2_^SOVi)Bi(?i#*WGC!0R$uk*HgX@>mMU!=!LpC7| zd)ArLAu?u%vnXH3xGA0U`d1g(IF9`vEq&bb?=sLoP=;LC7q;m4t<_s>$aAMIg(nVIwTJ-0QU5umH~Iv1`< z$E#fccf^#3k_~0A2;%&CcDKs&=%TYas`JijugG9K)R@^K;>_8;zvg>QUZWX}mVbFU zJcYNa{3@hVUmMEetv;V;u~fx{0B{r`l}%_n`^px-eY_uXdh5<&=r3{p(aP3sw-EL1(z@XEhnF&u-mT(9l+d_}=p zN)RTrWPRnG?_u!vAnslcy!DJnnwhKLpWC!E>pJ&6D$mvYskefZY9%{{t`ROf#TN}! z8R+F~+%17H2aWUs*fldCt1S7VL=rVPHSzNASPVtdt`&hFmo5T$UMiIKpnOnPoRJ1# zquOynr+o%;tiHN-LCC`^m)%?RSCj2G$m};TMRW5A7Q>cOTd2^@DS`U*OE!dY6%c* z0~Dvu?TpEn0GW7YzWUw9(AC{gd5mZS??1&E2yK46ohx|H`#rp{A!yVDdFMAjKhWu- zLc$3bmD^@yCo(`|q3W1$|EoC!QPUX@R$~h$pQ222oD1^9Um@e+3XYO)RDz<1dA4e> z;3q4t2!=yy70)i|8+1{wgdk8u}tHkX_zPh^C4T~_Jw`!2j@Qj zm^-KuaEX>Lu2-FURLe7~vO24~B=g#54c1%G`hk${3d?#K>s$Aw4o{z1Hsnv1Jv znjRvrcu;~-4r058qtP$F5^}nL7^3C3HERx+cMY4M7o!gr3M1)gXpyoM$IGI!8<+#Z z1o!=^T?{=zNXo3-1y)k`&PZt|9i(fAW)Q%E0k#f3*nC=h_Z_wYLqKgt8KH8Lq zWb&gkd0P7)Tts!wuz6D^lF6}iv7rer9!DR4POgHLw6sG>uqhvrP(<&)JWwb_)J2PX zwa07q52P6H%=Dc604oX2zab_zO&<}7!#hKd70M8G(d?`CctaBm-Jz-|$7Jqr+{p#sr+=k?cbh3vVSyE7Bnrgr2zswjvvq49j$WjjPQ*S7L zc8D(5XLF`B?GW9HD`Sa9tvb^3pzoM=2AX{J6l@wsbkWr(-m&9h_Z&jWs|Y!HBa{Do z(O!xDsy#~c)e!|%;$J&UIvCf*!hH;7Hn~E!(>y4}XHcdd{s71zi}JN>`KYgiOxnk= z-$EyWY(>Ni5>KGx9zyw)7&Y<&8Ag-3P^7)!f_VOo&d{?8oRJ3yHRy8E6{Bf%^u(lN zF>zhvjl-`~oMcsNpD-Ig50VQdG?_Q2dBB%9rcTL)bnbe6Lbt$XKL@IW>@46|Af`DP zW4Zc!Tk~a=g{yf=%eU-X~ItTd(D3e3f`>jQDM%1LY5Z!Q)(M;N@D@JlTYS^Z| z#co|+^4&qHlruhTE99uzFVxQTExXnhK(5Uins!_$TfReY;m6f-s2CTNnxu&}?|w%% z^q@rfC1kuX@OT4xe?ujqW-SDYhA66r;=LHwl`L025 z9su>glshv)-s_CUtWp{(Inr- zr#1hC#SuAcf|q1%0fg z$>~v1+hZ|h-KL~3Yz8^GCtjg14FiV|7TpGFuaj1{sgB&` zi=LL#{jUlOJyT6|5>vr7@9-&XakNTWecU*fgqsse#1!^%ko!xOwzLy*qum0ko>PzQ zEWiUGm)~$8cC}hWt$f|YS}Kd2ZY!WuLnAG>K6!g**AxF0t|v-$6uckn6s|``sS4ur z5lB{f!)S8@@ztxF3-9g26S$q&cStqsT`E^|Uw z(GGtd@3(f`sJl@G!t1(C4dozzxo|;>=0U}3?s2r-kkRRO(7sBn#!!N6UC&g2pUuQ{$@uvOOnqxwwj$|Zv; zm55drNCd{@ zrxxRkvJtIZv!3>X@zK0zxzL7?zK~}8@2>!v{e$k-%4f@K%7QQJEJj@aGNsYIEFwBGU@_?&v; zm~a)YgpaMgBZsEy&asg3)5aQN z!`7Amfsfpuf0`ZdFI67mGmd)u_N^4H#HHhMvD-1*@7VrMM<}#KAvFq}TR|l>s6KV&bA=;vwljCgluDQ`s2aD1h(hQ6; zolcLz)4H;pEyZ-^`irl-X8vmV;|ZT)LH0laMsF=nYIoT%D~&u24WHNVfBLnOa%uUT zs%rm|hClZQ+6BOUkqWX&5*zT>HAYcrtZfVRMxum0+(;|bgdWa)^3>+{m!I&(Oe!)3 zntR?{VDF$bg!8eXO@4b{h+0FlQVc#3UP8h7zvSQ_+3}5MQi9HU4E8Q*SZ&JaT#975 zQLz;5#IcuyL#kB%xccBmR0TxF84W}LJg6_}G#ufL(HTxM)*CROGy!SD-nVvixEEx1x znHop^lF_f@Wors_hVAgM=z(ecS7f8hbNDdEvS;Q_lF8E1tM5XV4|*T2t8*#|5{8XM zHrVyz(BIt002~Wk%nyGa|Ns3lvErQ?INUL>US$ss4jPw^&d=v%XG?WuTU?J?TBzkh z@oZ8(jz9tmT90a`frDnCmo(MG8Q_at`-@z7mjK22dxWSyIeBvR2a3h5D`wt=3ZB89l914aUgS1t7!-!H(0n_{32XoPH)wGoXoSCO* z{=?wl!vX@*wzjr)U%yTP!jaUCdn|~Ga*P~{HZNq7e)v)YPKo0hUp#*)X(j=MjVDf= zu*|>pV11cpHl_Q8PN@J1)S9DIn8{Xe6rlaIaLEneOLjliGcxLdC*>)+b>9Y|+P`m~ zyt*@Fr;`HLT@0W#S}8H->Kcqz1lSN1h6Tl%f&Rjl$vHOU6(G?dHRnrk)LcL&k`xyH zyCdDG2Of&{VOOs7yDc&p0FEJ`c7xWPb3IuzpvZULMg#hx*SDDS#TYUVfU=py-t-Q8 z{|S+E1()d#C-0ey*VFW$H#?zCjA!pm=wm76re0oN>wrV1=sB!iR$dMuECFbbqOq~D z#F#TgK?D@&?%?8Na*%|Q(haoP6J&q0)vDMv72uv|^~Z40aCCt0u}%juMPaZ2q$*#? z7jQf>0dK?=fPqFvMwULa3r%&I*}ja7Gti;IvU4VQFTxoh-eGHUXEPUg*_`K6(;O07@jjg$M zXJ^1#+eSsl#H0ac2#>1Rw#@<9RcZ756zzK|J#1X+DKggO|3nQ1GO>V-_NvF96*0j9 zl&I%366rL4w)aQT#$dW76<#K-0k*7ZivX*!`}@e~=rSO9=|mA$=>RL@YOx|7bf6zP z%3=As7R@1Qv)^|JQTc~4(JW0qdGh33o`Zu!1b#4R-rBsvCucKldJiWb_x_{Xc=}>G z$j8*%2$=$P_&X){Az&Lm?xYvC2+j7jTm3yfJxeM^Mn+89wv3vp&urZ0a-6)wS*NBr z`&C(a`47DffUd_5T7>^=U0v#6s~7!_fUvOee#$Lus>uJjm-zu2+?lknL#Jkv{OMk| za1g(;C|_gKR^$;7zy?+NF`U>}b|F`-q0KFYkJk1kI6%A|wl{Zn)}4KIFLJdD^iv8S z1B9wNa8pDF_ydKL3ijWHkY-@QSDT;kpKGt`q5(d@S?{rLtjcD|wBNr*2>=Jy1qeCe zv%*Nq{-0v52x$T)w*&%3x)Ld<>bLm5{;5FLbVoWZGbZ%t4uNgkAP;Dq?`UvM-6d-YNz1CZlFjGoy5@(o%!P+N-!-b^Sdc4#;# zpm`rM)EU=L!Fd6%U@XuFT?ggC_V%d4AZyZxDy;xGr40~s@QY(QARZFXat$zbrs;;S zJJSq~k=G%@z6^7P9E1Dd4l;R4FRTkR@J`1Sw#IJeF6a!9>*H{G0!j@G$kcro|y HTMz#S%Q@FW literal 0 HcmV?d00001 diff --git a/tutorials/physics/interpolation/img/physics_interpolation_mode.webp b/tutorials/physics/interpolation/img/physics_interpolation_mode.webp new file mode 100644 index 0000000000000000000000000000000000000000..c14c04877d8bae96264cf534324e0af96d4eab9a GIT binary patch literal 5714 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 E00Cb0ga7~l literal 0 HcmV?d00001 diff --git a/tutorials/physics/interpolation/index.rst b/tutorials/physics/interpolation/index.rst new file mode 100644 index 000000000000..92ce6c244840 --- /dev/null +++ b/tutorials/physics/interpolation/index.rst @@ -0,0 +1,14 @@ +.. _doc_physics_interpolation: + +Physics Interpolation +===================== + +.. toctree:: + :maxdepth: 1 + :name: toc-physics-interpolation + + physics_interpolation_quick_start_guide + physics_interpolation_introduction + using_physics_interpolation + advanced_physics_interpolation + 2d_and_3d_physics_interpolation diff --git a/tutorials/physics/interpolation/physics_interpolation_introduction.rst b/tutorials/physics/interpolation/physics_interpolation_introduction.rst new file mode 100644 index 000000000000..b4a8b6e3b5d4 --- /dev/null +++ b/tutorials/physics/interpolation/physics_interpolation_introduction.rst @@ -0,0 +1,241 @@ +.. _doc_physics_interpolation_introduction: + +Introduction +============ + +Physics ticks and rendered frames +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +One key concept to understand in Godot is the distinction between physics +ticks (sometimes referred to as iterations or physics frames), and rendered +frames. The physics proceeds at a fixed tick rate +(set in :ref:`ProjectSettings.physics/common/physics_fps`), +which defaults to 60 ticks per second. + +However, the engine does not necessarily **render** at the same rate. +Although many monitors refresh at 60 Hz (cycles per second), many refresh at +completely different frequencies (e.g. 75 Hz, 144 Hz, 240 Hz or more). Even +though a monitor may be able to show a new frame e.g. 60 times a second, +there is no guarantee that the CPU and GPU will be able to *supply* frames at +this rate. For instance, when running with V-Sync, the computer may be too +slow for 60 and only reach the deadlines for 30 FPS, in which case the frames +you see will change at 30 FPS (resulting in stuttering). + +But there is a problem here. What happens if the physics ticks do not +coincide with frames? What happens if the physics tick rate is out of phase +with the frame rate? Or worse, what happens if the physics tick rate is +*lower* than the rendered frame rate? + +This problem is easier to understand if we consider an extreme scenario. If +you set the physics tick rate to 10 ticks per second, in a simple game with +a rendered frame rate of 60 FPS. If we plot a graph of the positions of an +object against the rendered frames, you can see that the positions will +appear to "jump" every 1/10th of a second, rather than giving a smooth +motion. When the physics calculates a new position for a new object, it is +not rendered in this position for just one frame, but for 6 frames. + +.. image:: img/fti_graph_fixed_ticks.png + +This jump can be seen in other combinations of tick / frame rate as glitches, +or jitter, caused by this staircasing effect due to the discrepancy between +physics tick time and rendered frame time. + +What can we do about frames and ticks being out of sync? +-------------------------------------------------------- + +Lock the tick / frame rate together? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The most obvious solution is to get rid of the problem, by ensuring there is +a physics tick that coincides with every frame. This used to be the approach +on old consoles and fixed hardware computers. If you know that every player +will be using the same hardware, you can ensure it is fast enough to +calculate ticks and frames at e.g. 50 FPS, and you will be sure it will work +great for everybody. + +However, modern games are often no longer made for fixed hardware. You will +often be planning to release on desktop computers, mobiles and more, all of +which have huge variations in performance, as well as different monitor +refresh rates. We need to come up with a better way of dealing with the +problem. + +Adapt the tick rate? +^^^^^^^^^^^^^^^^^^^^ + +Instead of designing the game at a fixed physics tick rate, we could allow +the tick rate to scale according to the end users hardware. We could for +example use a fixed tick rate that works for that hardware, or even vary +the duration of each physics tick to match a particular frame duration. + +This works, but there is a problem. Physics (*and game logic*, which is often +also run in the ``_physics_process``) work best and most consistently when +run at a **fixed**, predetermined tick rate. If you attempt to run a racing +game physics that has been designed for 60 TPS (ticks per second) at e.g. 10 +TPS, the physics will behave completely differently. Controls may be less +responsive, collisions / trajectories can be completely different. You may +test your game thoroughly at 60 TPS, then find it breaks on end users +machines when it runs at a different tick rate. + +This can make quality assurance difficult with hard to reproduce bugs, +especially in AAA games where problems of this sort can be very costly. This +can also be problematic for multiplayer games for competitive integrity, as +running the game at certain tick rates may be more advantageous than others. + +Lock the tick rate, but use interpolation to smooth frames in between physics ticks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This has become one of the most popular approaches to dealing with the +problem. It is supported by Godot 3.5 and later in 3D (although it is +optional and disabled by default). + +We have established that the most desirable physics/game logic arrangement +for consistency and predictability is a physics tick rate that is fixed at +design-time. The problem is the discrepancy between the physics position +recorded, and where we "want" a physics object to be shown on a frame to give +smooth motion. + +The answer turns out to be simple, but can be a little hard to get your head +around at first. + +Instead of keeping track of just the current position of a physics object in +the engine, we keep track of *both the current position of the object, and +the previous position* on the previous physics tick. + +Why do we need the previous position *(in fact the entire transform, +including rotation and scaling)*? By using a little math magic, we can use +**interpolation** to calculate what the transform of the object would be +between those two points, in our ideal world of smooth continuous movement. + +.. image:: img/fti_graph_interpolated.png + +Linear interpolation +^^^^^^^^^^^^^^^^^^^^ + +The simplest way to achieve this is linear interpolation, or lerping, which +you may have used before. + +Let us consider only the position, and a situation where we know that the +previous physics tick X coordinate was 10 units, and the current physics tick +X coordinate is 30 units. + +.. note:: Although the maths is explained here, you do not have to worry + about the details, as this step will be performed for you. Under + the hood, Godot may use more complex forms of interpolation, but + linear interpolation is the easiest in terms of explanation. + +The physics interpolation fraction +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If our physics ticks are happening 10 times per second (for this example), +what happens if our rendered frame takes place at time 0.12 seconds? We can +do some math to figure out where the object would be to obtain a smooth +motion between the two ticks. + +First of all, we have to calculate how far through the physics tick we want +the object to be. If the last physics tick took place at 0.1 seconds, we are +0.02 seconds *(0.12 - 0.1)* through a tick that we know will take 0.1 seconds +(10 ticks per second). The fraction through the tick is thus: + +.. code-block:: python + + fraction = 0.02 / 0.10 + fraction = 0.2 + +This is called the **physics interpolation fraction**, and is handily +calculated for you by Godot. It can be retrieved on any frame by calling +:ref:`Engine.get_physics_interpolation_fraction`. + +Calculating the interpolated position +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Once we have the interpolation fraction, we can insert it into a standard +linear interpolation equation. The X coordinate would thus be: + +.. code-block:: python + + x_interpolated = x_prev + ((x_curr - x_prev) * 0.2) + +So substituting our ``x_prev`` as 10, and ``x_curr`` as 30: + +.. code-block:: python + + x_interpolated = 10 + ((30 - 10) * 0.2) + x_interpolated = 10 + 4 + x_interpolated = 14 + +Let's break that down: + +- We know the X starts from the coordinate on the previous tick (``x_prev``) + which is 10 units. +- We know that after the full tick, the difference between the current tick + and the previous tick will have been added (``x_curr - x_prev``) (which is + 20 units). +- The only thing we need to vary is the proportion of this difference we add, + according to how far we are through the physics tick. + +.. note:: Although this example interpolates the position, the same thing can + be done with the rotation and scale of objects. It is not necessary to + know the details as Godot will do all this for you. + +Smoothed transformations between physics ticks? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Putting all this together shows that it should be possible to have a nice +smooth estimation of the transform of objects between the current and +previous physics tick. + +But wait, you may have noticed something. If we are interpolating between the +current and previous ticks, we are not estimating the position of the object +*now*, we are estimating the position of the object in the past. To be exact, +we are estimating the position of the object *between 1 and 2 ticks* into the +past. + +In the past +^^^^^^^^^^^ + +What does this mean? This scheme does work, but it does mean we are +effectively introducing a delay between what we see on the screen, and where +the objects *should* be. + +In practice, most people won't notice this delay, or rather, it is typically +not *objectionable*. There are already significant delays involved in games, +we just don't typically notice them. The most significant effect is there can +be a slight delay to input, which can be a factor in fast twitch games. In +some of these fast input situations, you may wish to turn off physics +interpolation and use a different scheme, or use a high tick rate, which +mitigates these delays. + +Why look into the past? Why not predict the future? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There is an alternative to this scheme, which is: instead of interpolating +between the previous and current tick, we use maths to *extrapolate* into the +future. We try to predict where the object *will be*, rather than show it +where it was. This can be done and may be offered as an option in future, but +there are some significant downsides: + +- The prediction may not be correct, especially when an object collides with + another object during the physics tick. +- Where a prediction was incorrect, the object may extrapolate into an + "impossible" position, like inside a wall. +- Providing the movement speed is slow, these incorrect predictions may not + be too much of a problem. +- When a prediction was incorrect, the object may have to jump or snap back + onto the corrected path. This can be visually jarring. + +Fixed timestep interpolation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In Godot this whole system is referred to as physics interpolation, but you +may also hear it referred to as **"fixed timestep interpolation"**, as it is +interpolating between objects moved with a fixed timestep (physics ticks per +second). In some ways the second term is more accurate, because it can also +be used to interpolate objects that are not driven by physics. + +.. tip:: Although physics interpolation is usually a good choice, there are + exceptions where you may choose not to use Godot's built-in physics + interpolation (or use it in a limited fashion). An example category + is internet multiplayer games. Multiplayer games often receive tick + or timing based information from other players or a server and these + may not coincide with local physics ticks, so a custom interpolation + technique can often be a better fit. diff --git a/tutorials/physics/interpolation/physics_interpolation_quick_start_guide.rst b/tutorials/physics/interpolation/physics_interpolation_quick_start_guide.rst new file mode 100644 index 000000000000..9c93ec501abe --- /dev/null +++ b/tutorials/physics/interpolation/physics_interpolation_quick_start_guide.rst @@ -0,0 +1,15 @@ +.. _doc_physics_interpolation_quick_start_guide: + +Quick start guide +================= + +- Turn on physics interpolation: :ref:`ProjectSettings.physics/common/physics_interpolation` +- Make sure you move objects and run your game logic in + ``_physics_process()`` rather than ``_process()``. This includes moving + objects directly *and indirectly* (by e.g. moving a parent, or using + another mechanism to automatically move nodes). +- Be sure to call :ref:`Node.reset_physics_interpolation` + on nodes *after* you first position or teleport them, to prevent + "streaking" +- Temporarily try setting :ref:`ProjectSettings.physics/common/physics_fps` + to 10 to see the difference with and without interpolation. diff --git a/tutorials/physics/interpolation/using_physics_interpolation.rst b/tutorials/physics/interpolation/using_physics_interpolation.rst new file mode 100644 index 000000000000..e7e7932b4f91 --- /dev/null +++ b/tutorials/physics/interpolation/using_physics_interpolation.rst @@ -0,0 +1,158 @@ +.. _doc_using_physics_interpolation: + +Using physics interpolation +=========================== +How do we incorporate physics interpolation into a Godot game? Are there any +caveats? + +We have tried to make the system as easy to use as possible, and many +existing games will work with few changes. That said there are some +situations which require special treatment, and these will be described. + +Turn on the physics interpolation setting +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The first step is to turn on physics interpolation in :ref:`ProjectSettings.physics/common/physics_interpolation`. +You can now run your game. + +It is likely that nothing looks hugely different, particularly if you are +running physics at 60 TPS or a multiple of it. However, quite a bit more is +happening behind the scenes. + +.. tip:: + + To convert an existing game to use interpolation, it is highly + recommended that you temporarily set :ref:`ProjectSettings.physics/common/physics_fps` + to a low value such as 10, which will make interpolation problems more + obvious. + +Move (almost) all game logic from _process to _physics_process +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The most fundamental requirement for physics interpolation (which you may be +doing already) is that you should be moving and performing game logic on your +objects within ``_physics_process`` (which runs at a physics tick) rather +than ``_process`` (which runs on a rendered frame). This means your scripts +should typically be doing the bulk of their processing within +``_physics_process``, including responding to input and AI. + +Setting the transform of objects only within physics ticks allows the +automatic interpolation to deal with transforms *between* physics ticks, and +ensures the game will run the same whatever machine it is run on. As a bonus, +this also reduces CPU usage if the game is rendering at high FPS, since AI +logic (for example) will no longer run on every rendered frame. + +.. note:: If you attempt to set the transform of interpolated objects + *outside* the physics tick, the calculations for the interpolated + position will be incorrect, and you will get jitter. This jitter + may not be visible on your machine, but it *will* occur for some + players. For this reason, setting the transform of interpolated + objects should be avoided outside of the physics tick. Godot will + attempt to produce warnings in the editor if this case is detected. + +.. tip:: This is only a *soft-rule*. There are some occasions where you might + want to teleport objects outside of the physics tick (for instance when + starting a level, or respawning objects). Still, in general, you should + be applying transforms from the physics tick. + + +Ensure that all indirect movement happens during physics ticks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Consider that in Godot, Nodes can be moved not just directly in your own +scripts, but also by automatic methods such as tweening, animation, and +navigation. All these methods should also have their timing set to operate on +the physics tick rather than each frame ("idle"), **if** you are using them +to move objects (*these methods can also be used to control properties that +are not interpolated*). + +.. note:: Also consider that nodes can be moved not just by moving + themselves, but also by moving parent nodes in the :ref:`SceneTree`. + The movement of parents should therefore also only occur during + physics ticks. + +Choose a physics tick rate +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When using physics interpolation, the rendering is decoupled from physics, +and you can choose any value that makes sense for your game. You are no +longer limited to values that are multiples of the user's monitor refresh +rate (for stutter-free gameplay if the target FPS is reached). + +As a rough guide: + +.. csv-table:: + :header: "Low tick rates (10-30)", "Medium tick rates (30-60)", "High tick rates (60+)" + :widths: 20, 20, 20 + + "Better CPU performance","Good physics behaviour in complex scenes","Good with fast physics" + "Add some delay to input","Good for first person games","Good for racing games" + "Simple physics behaviour" + +.. note:: You can always change the tick rate as you develop, it is as simple + as changing the project setting. + +Call reset_physics_interpolation() when teleporting objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Most of the time, interpolation is what you want between two physics ticks. +However, there is one situation in which it may *not* be what you want. That +is when you are initially placing objects, or moving them to a new location. +Here, you don't want a smooth motion between where the object was (e.g. the +origin) and the initial position - you want an instantaneous move. + +The solution to this is to call the :ref:`Node.reset_physics_interpolation` +function. What this function does under the hood is set the internally stored +*previous transform* of the object to be equal to the *current transform*. +This ensures that when interpolating between these two equal transforms, +there will be no movement. + +Even if you forget to call this, it will usually not be a problem in most +situations (especially at high tick rates). This is something you can easily +leave to the polishing phase of your game. The worst that will happen is +seeing a streaking motion for a frame or so when you move them - you will +know when you need it! + +There are actually two ways to use ``reset_physics_interpolation()``: + +*Standing start (e.g. player)* + +1) Set the initial transform +2) Call ``reset_physics_interpolation()`` + +The previous and current transforms will be identical, resulting in no +initial movement. + +*Moving start (e.g. bullet)* + +1) Set the initial transform +2) Call ``reset_physics_interpolation()`` +3) Immediately set the transform expected after the first tick of motion + +The previous transform will be the starting position, and the current +transform will act as though a tick of simulation has already taken place. +This will immediately start moving the object, instead of having a tick delay +standing still. + +.. important:: Make sure you set the transform and call + ``reset_physics_interpolation()`` in the correct order as + shown above, otherwise you will see unwanted "streaking". + +Testing and debugging tips +-------------------------- + +Even if you intend to run physics at 60 TPS, in order to thoroughly test your +interpolation and get the smoothest gameplay, it is highly recommended to +temporarily set the physics tick rate to a low value such as 10 TPS. + +The gameplay may not work perfectly, but it should enable you to more easily +see cases where you should be calling :ref:`Node.reset_physics_interpolation`, +or where you should be using your own custom interpolation on e.g. a :ref:`Camera`. +Once you have these cases fixed, you can set the physics tick rate back to +the desired setting. + +The other great advantage to testing at a low tick rate is you can often +notice other game systems that are synchronized to the physics tick and +creating glitches which you may want to work around. Typical examples include +setting animation blend values, which you may decide to set in ``_process()`` +and interpolate manually.