Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question How to Interpolate Runtime Created Entities ?

Discussion in 'Physics for ECS' started by Opeth001, Sep 24, 2020.

  1. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,112
    Hello Everyone,

    I upgraded the Unity Physics Package to 0.5 and i saw that unity finally added the Interpolation/Extrapolation feature.
    for simple use cases it's super simple, i just have to activate the interpolation on the Physics Body. but what about Entities that don't have a Rigid Body ?

    EG:
    Projectiles move in the FixedStepSimulationSystemGroup from one point to another raycasting for possible hits. when something is hit, events are spawned and projectile entity is destroy.
    the projectile entity only contains projectile data and transformation components, for the moment a second entity is created with an Addressable GO linked to it having the Effects which will follow the parent entity and interpolate. (my custom implementation)

    but how can i migrate to the Official Interpolation interpolating an entity without a PhysicsShape and PhysicsBody?
     
    Last edited: Sep 24, 2020
  2. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    I'd also really like if interpolation could be separate from physics components, so that we could add the exact same interpolation logic to any non-physics objects of our own

    I think currently, InterpolationWithVelocity and Extrapolation relies on PhysicsVelocity and PhysicsMass (for the center-of-mass), but these could simply be replaced with float3 velocities and float3 center-of-rotation. This would allow them to be totally generic, I believe
     
    Last edited: Sep 25, 2020
    petarmHavok and steveeHavok like this.
  3. desertGhost_

    desertGhost_

    Joined:
    Apr 12, 2018
    Posts:
    259
    There is a function built into Unity Physic's GraphicalSmoothingUtility that interpolates based on the difference in Position and Orientation. I use it for smoothing the movement of a custom kinematic character controller system and it works really well (as long as you don't mind the compromises described in the comments).

    Code (CSharp):
    1. /// <summary>
    2.         /// Compute a simple interpolated transform for the graphical representation of a rigid body between <c>previousTransform</c> and <c>currentTransform</c>.
    3.         /// Because bodies' motion is often deflected during a physics step when there is a contact event, using this method can make bodies appear to change direction before a collision is actually visible.
    4.         /// <code>
    5.         /// Simulation:                Graphical Interpolation:
    6.         ///
    7.         ///               O (t=2)                     O (t=2)
    8.         /// (t=0) O      /               (t=0) O     o
    9.         ///        \    /                        o  o
    10.         ///         \  O (t=1)                     O (t=1)
    11.         /// _________\/_________       ____________________
    12.         ///
    13.         /// </code>
    14.         /// (Note that for cartoons, an animator would use squash and stretch to force a body to make contact even if it is technically not hitting on a specific frame.)
    15.         /// See <see cref="InterpolateUsingVelocity"/> for an alternative approach.
    16.         /// </summary>
    17.         /// <param name="previousTransform">The transform of the rigid body before physics stepped.</param>
    18.         /// <param name="currentTransform">The transform of the rigid body after physics has stepped (i.e., the value of its <c>Translation</c> and <c>Rotation</c> components).</param>
    19.         /// <param name="normalizedTimeAhead">A value in the range [0, 1] indicating how many seconds the current elapsed time for graphics is ahead of the elapsed time when physics last stepped, as a proportion of the fixed timestep used by physics.</param>
    20.         /// <returns>
    21.         /// An interpolated transform for a rigid body's graphical representation, suitable for constructing its <c>LocalToWorld</c> matrix before rendering.
    22.         /// See also <seealso cref="BuildLocalToWorld"/>.
    23.         /// </returns>
    24.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    25.         public static RigidTransform Interpolate(
    26.             in RigidTransform previousTransform,
    27.             in RigidTransform currentTransform,
    28.             float normalizedTimeAhead
    29.         )
    30.         {
    31.             return new RigidTransform(
    32.                 math.nlerp(previousTransform.rot, currentTransform.rot, normalizedTimeAhead),
    33.                 math.lerp(previousTransform.pos, currentTransform.pos, normalizedTimeAhead)
    34.             );
    35.         }
     
    Opeth001 likes this.
  4. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,112
    Thanks !
    Can you share an example ?
    Where do you get the normalizedTimeAhead value ?
     
  5. desertGhost_

    desertGhost_

    Joined:
    Apr 12, 2018
    Posts:
    259
    Please note that my code is largely just a modification of the existing smoothing systems to use Interpolate using position and orientation instead of velocity and angular velocity.

    This system actually modifies the
    LocalToWorld
    of smoothed entities:

    Code (CSharp):
    1.    
    2.     [UpdateInGroup(typeof(TransformSystemGroup))]
    3.     [UpdateAfter(typeof(EndFrameLocalToParentSystem))]
    4.     [UpdateAfter(typeof(EndFrameTRSToLocalToWorldSystem))]
    5.     [UpdateAfter(typeof(EndFrameWorldToLocalSystem))]
    6.     public class KinematicCharacterSmoothingSystem : SystemBase
    7.     {
    8.         [BurstCompile]
    9.         struct KinematicCharacterSmoothingJob : IJobChunk
    10.         {
    11.             [ReadOnly]
    12.             public ComponentTypeHandle<Translation> translationType;
    13.  
    14.             [ReadOnly]
    15.             public ComponentTypeHandle<Rotation> rotationType;
    16.  
    17.             [ReadOnly]
    18.             public ComponentTypeHandle<NonUniformScale> nonUniformScaleType;
    19.  
    20.             [ReadOnly]
    21.             public ComponentTypeHandle<Scale> scaleType;
    22.  
    23.             [ReadOnly]
    24.             public ComponentTypeHandle<CompositeScale> compositeScaleType;
    25.  
    26.             public ComponentTypeHandle<KinematicCharacterSmoothing> characterSmoothingType;
    27.  
    28.             public ComponentTypeHandle<LocalToWorld> localToWorldType;
    29.  
    30.             public float normalizedTimeAhead;
    31.  
    32.             public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    33.             {
    34.                 var hasNonUniformScale = chunk.Has(nonUniformScaleType);
    35.                 var hasScale = chunk.Has(scaleType);
    36.                 var hasAnyScale = hasNonUniformScale || hasScale || chunk.Has(compositeScaleType);
    37.  
    38.                 NativeArray<Translation> positions = chunk.GetNativeArray(translationType);
    39.                 NativeArray<Rotation> orientations = chunk.GetNativeArray(rotationType);
    40.                 NativeArray<NonUniformScale> nonUniformScales = chunk.GetNativeArray(nonUniformScaleType);
    41.                 NativeArray<Scale> scales = chunk.GetNativeArray(scaleType);
    42.                 NativeArray<CompositeScale> compositeScales = chunk.GetNativeArray(compositeScaleType);
    43.                 NativeArray<LocalToWorld> localToWorlds = chunk.GetNativeArray(localToWorldType);
    44.                 NativeArray<KinematicCharacterSmoothing> smoothings = chunk.GetNativeArray(characterSmoothingType);
    45.  
    46.                 int index;
    47.  
    48.                 for (index = 0; index < chunk.Count; index++)
    49.                 {
    50.                     var smoothing = smoothings[index];
    51.  
    52.                     RigidTransform transform = new RigidTransform(orientations[index].Value, positions[index].Value);
    53.  
    54.                     if (smoothing.teleported)
    55.                     {
    56.                         smoothing.teleported = false;
    57.                         smoothings[index] = smoothing;
    58.                     }
    59.                     else
    60.                     {
    61.                         transform = GraphicalSmoothingUtility.Interpolate(smoothing.lastTransform,
    62.                                                                           transform,
    63.                                                                           normalizedTimeAhead);
    64.                     }
    65.  
    66.                     localToWorlds[index] = GraphicalSmoothingUtility.BuildLocalToWorld
    67.                     (
    68.                         index,
    69.                         transform,
    70.                         hasAnyScale,
    71.                         hasNonUniformScale,
    72.                         nonUniformScales,
    73.                         hasScale,
    74.                         scales,
    75.                         compositeScales
    76.                     );
    77.                 }
    78.             }
    79.         }
    80.  
    81.         RecordMostRecentFixedTime recordMostRecentFixedTime;
    82.  
    83.         EntityQuery query;
    84.         protected override void OnCreate()
    85.         {
    86.             base.OnCreate();
    87.  
    88.             query = GetEntityQuery(new EntityQueryDesc
    89.             {
    90.                 All = new[]
    91.                 {
    92.                     ComponentType.ReadOnly<Translation>(),
    93.                     ComponentType.ReadOnly<Rotation>(),
    94.                     ComponentType.ReadWrite<KinematicCharacterSmoothing>(),
    95.                     ComponentType.ReadWrite<LocalToWorld>()
    96.                 },
    97.                 None = new[]
    98.                 {
    99.                     ComponentType.ReadOnly<PhysicsGraphicalSmoothing>()
    100.                 }
    101.             });
    102.  
    103.             recordMostRecentFixedTime = World.GetOrCreateSystem<RecordMostRecentFixedTime>();
    104.         }
    105.         protected override void OnUpdate()
    106.         {
    107.             if (query.CalculateEntityCount() > 0)
    108.             {
    109.                 var timeAhead = (float)(Time.ElapsedTime - recordMostRecentFixedTime.MostRecentElapsedTime);
    110.                 var timeStep = (float)recordMostRecentFixedTime.MostRecentDeltaTime;
    111.  
    112.                 if (timeAhead <= 0f || timeStep == 0f)
    113.                 {
    114.                     return;
    115.                 }
    116.  
    117.                 var normalizedTimeAhead = math.clamp(timeAhead / timeStep, 0f, 1f);
    118.  
    119.                 Dependency = new KinematicCharacterSmoothingJob
    120.                 {
    121.                     characterSmoothingType = GetComponentTypeHandle<KinematicCharacterSmoothing>(),
    122.                     localToWorldType = GetComponentTypeHandle<LocalToWorld>(),
    123.                     translationType = GetComponentTypeHandle<Translation>(true),
    124.                     rotationType = GetComponentTypeHandle<Rotation>(true),
    125.                     scaleType = GetComponentTypeHandle<Scale>(true),
    126.                     compositeScaleType = GetComponentTypeHandle<CompositeScale>(true),
    127.                     nonUniformScaleType = GetComponentTypeHandle<NonUniformScale>(true),
    128.                     normalizedTimeAhead = normalizedTimeAhead
    129.                 }.ScheduleParallel(query, Dependency);
    130.  
    131.             }
    132.         }
    133.     }
    It must run after all built in systems writing to
    LocalToWorld
    have run.

    This system runs in the Fixed Step and before my Kinematic Character System. It just stores the last position and orientation of the smoothed entities:

    Code (CSharp):
    1.  
    2.     [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    3.     [UpdateAfter(typeof(BuildPhysicsWorld)), UpdateBefore(typeof(ExportPhysicsWorld))]
    4.     [UpdateBefore(typeof(KinematicCharacterSystem))]
    5.     public class KinematicCharacterSmoothingTransformStashSystem : SystemBase
    6.     {
    7.         KinematicCharacterSystem characterSystem;
    8.  
    9.         protected override void OnCreate()
    10.         {
    11.             base.OnCreate();
    12.             characterSystem = World.GetOrCreateSystem<KinematicCharacterSystem>();
    13.         }
    14.  
    15.         protected override void OnUpdate()
    16.         {
    17.             Dependency = Entities.ForEach((ref KinematicCharacterSmoothing smoothing,
    18.                                            in Translation position, in Rotation orientation) =>
    19.             {
    20.                 smoothing.lastTransform = new RigidTransform(orientation.Value, position.Value);
    21.  
    22.             }).WithName("UpdateKinematicCharacterSmoothingTransform").ScheduleParallel(Dependency);
    23.  
    24.             characterSystem.AddInputDependency(Dependency);
    25.         }
    26.     }

    There is a system built into Unity Physics
    RecordMostRecentFixedTime
    that tracks the most recent time that fixed step has run.

    The normalizedTimeAhead is computed as such and is computed in the OnUpdate method of the smoothing system (it's done the same way as in the Unity Physics smoothing system):

    Code (CSharp):
    1.  
    2. var timeAhead = (float)(Time.ElapsedTime - recordMostRecentFixedTime.MostRecentElapsedTime);
    3. var timeStep = (float)recordMostRecentFixedTime.MostRecentDeltaTime;
    4. if (timeAhead <= 0f || timeStep == 0f)
    5. {
    6.     return;
    7. }
    8. var normalizedTimeAhead = math.clamp(timeAhead / timeStep, 0f, 1f);
    9.  
     
    Opeth001 and steveeHavok like this.