Interpolation

Overview

Depending on the settings in our project, data may not always arrive at a smooth 60 frames per second through the network. This is completely okay, but in order to make state changes (e.g. movement, rotation) appear smooth on the client, we use interpolation.

Interpolation is a type of estimation, a method of constructing new data points within the range of a discrete set of known data points.

The way interpolation works in coherence is that we wait for three data points and then start smoothing the subsequent values according to the interpolation parameters defined in the schema (or default parameters, if no overrides are present).

Overriding interpolation settings

To override interpolation settings, create a new schema asset via Assets/Create/coherence/Schema. Once created, mark it as an active schema in the settings window.

In this new schema, add an interpolation definition:

interpolation MyInterpolation
  overshootPercentage 0
  overshootRetraction 0.0
  manualLatency 1
  autoLatencyFactor 1.0
  elasticity 0.05
  maxDistance 1
  curveType "Linear"

This interpolation definition MyInterpolation isn't used yet. We need to tell coherence which component is going to use it. We can associate it to WorldPosition and WorldOrientation:

component WorldPosition [priority "high"]
  value Vector3 [bits "24", scale "101000", interpolation "MyInterpolation"]

component WorldOrientation [priority "high"]
  value Quaternion [bits "24", scale "101000", interpolation "MyInterpolation"]

Although not necessary, we're also defining scale and bit depth in the example above.

There are a few fields you can tune to get the most out of interpolation:

  • overshootPercentage

    • How far into dead reckoning we can venture when the time fraction exceeds 100%

  • overshootRetraction

    • How fast to pull back to 100% if we overshoot the allowed dead reckoning maximum in seconds

  • manualLatency

    • Seconds to stay behind the sample data

  • autoLatencyFactor

    • targetInterpolation = autoLatencyFactor * packetDeltaTime . -1 disables auto latency

  • elasticity

    • Seconds to remain behind the current interpolation point, applied to smoothdamp function or slerp

  • maxDistance

    • maximum distance between data points before the component "teleports" to the next value without smoothing.

    • allowed values: positive real numbers

  • curveType

    • the type of interpolation used

    • allowed values: "CatmullRom", "Linear"

Interpolation can be overridden globally, per component and per LOD level. Here is a sample schema that defines a named interpolation style OverrideTranslation and uses it for all WorldPosition components.

interpolation OverrideTranslation
  overshootPercentage 0
  overshootRetraction 0
  manualLatency 1
  autoLatencyFactor 1.1
  elasticity 0
  maxDistance 50000
  curveType "CatmullRom"
  
component WorldPosition
  value Vector3 [bits "24", scale "101000", interpolation "OverrideTranslation"]
  
archetype Destroyer
  lod 0
    WorldPosition
      value [bits "24", scale "101000", interpolation "OverrideTranslation"]
    Ship
  lod 1 [distance "25000"]
    WorldPosition
      value [bits "20", scale "101000", interpolation "OverrideTranslation"]
    Ship
  lod 2 [distance "40000"]
    WorldPosition
      value [bits "16", scale "101000", interpolation "OverrideTranslation"]
    Ship
  lod 3 [distance "70000"]
    WorldPosition
      value [bits "12", scale "101000", interpolation "OverrideTranslation"]
    Ship

Here's another example that overrides interpolation for WorldPosition and WorldOrientation:

interpolation OverrideTranslation
  overshootPercentage 2
  overshootRetraction 0.25
  manualLatency 1
  autoLatencyFactor 1.2
  elasticity 0.045
  maxDistance 1024
  curveType "Linear"

interpolation OverrideRotation
  overshootPercentage 0
  overshootRetraction 0.0
  manualLatency 1
  autoLatencyFactor 1.0
  elasticity 0.05
  maxDistance 1
  curveType "Linear"
  
component WorldPosition [priority "high"]
  value Vector3 [bits "24", scale "101000", interpolation "OverrideTranslation"]

component WorldOrientation [priority "high"]
  value Quaternion [bits "24", scale "101000", interpolation "OverrideTranslation"]

Remember that everytime you change a schema, you need to Bake for the changes to make effect.

We need one last piece to tie everything together. We need a way to let our bindings to use this interpolation override. To do so, we can create a custom binding provider that extends how the transform component is bound. Create a file named CustomTransformBindingProvider.cs in an Editor folder (e.g. Assets/Editor), with these contents:

using System.Collections.Generic;
using UnityEngine;
using Coherence.Toolkit;

[CustomBindingProvider(typeof(Transform))]
public class CustomTransformBindingProvider : TransformBindingProvider
{
    public override void FetchDescriptors(Component component, List<CustomBinding.Descriptor> descriptors)
    {
        base.FetchDescriptors(component, descriptors);
        descriptors[0].interpolationComponentName = "OverrideTranslation";
        descriptors[1].interpolationComponentName = "OverrideRotation";
    }
}

This will use OverrideTranslation interpolation component for WorldPosition, and OverrideRotation for WorldOrientation. You can use any name for these as long as it matches the names you defined in your schema. If you don't want to override one of these, just comment out the assignment you don't need.

After you compile this script, a popup will appear letting you know that your CoherenceSync assets have been updated to point to these new components you defined.

From here on, any additional change to interpolation you want to make, you can adjust the values in the schema and bake again.

Last updated