Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Built with Unity 6.5

### Added

- Rolling resistance as a separate physics-material property for sustained ball contacts, including authoring, packaging, deterministic verification, and calibration guidance ([#363](https://github.com/freezy/VisualPinball.Engine/issues/363)).
- Make packaging functional ([#557](https://github.com/freezy/VisualPinball.Engine/pull/557))
- New threading model ([#552](https://github.com/freezy/VisualPinball.Engine/pull/552))
- Free transformation ([#500](https://github.com/freezy/VisualPinball.Engine/pull/500))
Expand Down Expand Up @@ -67,6 +68,7 @@ Built with Unity 6.5
- Put game-, mesh-, collision- animation data into separate components ([#227](https://github.com/freezy/VisualPinball.Engine/pull/227), [Documentation](https://docs.visualpinball.org/creators-guide/editor/unity-components.html)).

### Fixed
- Sustained ball contact friction now uses mass- and substep-consistent Coulomb impulses while preserving impact friction behavior ([#363](https://github.com/freezy/VisualPinball.Engine/issues/363)).
- Disappearing objects due to wrong bounding box ([#441](https://github.com/freezy/VisualPinball.Engine/pull/441)).
- Default table import ([#434](https://github.com/freezy/VisualPinball.Engine/pull/434))
- Remaining ball spinning issue should now be solved ([#397](https://github.com/freezy/VisualPinball.Engine/pull/397)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,16 @@ We refer to how the material interacts with the ball during the physics simulati

- **Elasticity** - The bounciness, how much the ball is thrown back when it collides.
- **Elasticity Falloff** - Pinball tables have a lot of rubber parts, and rubber has a special attribute: it gets less bouncy when hit at higher velocity. The falloff parameter controls how much.
- **Friction** - How much friction is applied when the ball rolls along this material.
- **Friction** - The Coulomb coefficient that controls sliding, grip, and tangential collision and contact impulses.
- **Rolling Resistance** - A dimensionless coefficient (`Crr`) that removes energy from sustained near-no-slip rolling without changing impact response.
- **Scatter** - Adds a random factor to the collision angle.

Physics materials are a way to group common behavior among certain objects, but contrarily to rendered materials, you can also *not* assign a physics material to an object and set each of those four parameters individually.
Physics materials are a way to group common behavior among certain objects, but contrarily to rendered materials, you can also *not* assign a physics material to an object and set each parameter individually.

Friction and rolling resistance are separate controls. Increasing friction gives the ball more grip and changes oblique contact behavior. Increasing rolling resistance slows a ball that is already rolling without slip. Rolling resistance is applied only to sustained generic surface contacts; it does not affect impacts, ball-to-ball contacts, or the specialized flipper contact solver.

> [!IMPORTANT]
> Rolling resistance defaults to zero. VPE does not recommend a nonzero preset until measurements on a physical playfield have been completed. See [Calibrating Rolling Resistance](xref:rolling-resistance-calibration) before choosing a value.

> [!note]
> In Visual Pinball, the physical parameters are part of the rendered material, so there is only one notion of material.
Expand All @@ -42,4 +48,4 @@ As mentioned above, there are two differences between Visual Pinball and VPE how

When importing a `.vpx` file, VPE converts the "visual part" of Visual Pinball materials into materials for the current render pipeline. It does that by creating a new material for every material/texture combination in Visual Pinball. The materials are then written to the `Materials` asset folder of the imported table where they can be easily edited and referenced. Since Visual Pinball uses different shaders than Unity, the results of the conversion are approximations and should be heavily tweaked.

Since VPE uses the same physics engine as Visual Pinball, the physical values of the materials don't need to be converted, they are copied 1:1 into a new physics material and saved in the asset folder.
Since VPE uses the same physics engine as Visual Pinball, the physical values that exist in Visual Pinball are copied 1:1 into a new physics material and saved in the asset folder. VPX does not contain a rolling-resistance value, so imported physics materials start with `Rolling Resistance = 0` unless the table author opts in afterward.
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
uid: rolling-resistance-calibration
title: Calibrating Rolling Resistance
description: How to measure, fit, and validate rolling resistance for a pinball playfield.
---

# Calibrating Rolling Resistance

Rolling resistance controls energy loss while a ball is in sustained, near-no-slip contact with a surface. It is represented by the dimensionless coefficient `Crr`. It is separate from **Friction**, which controls sliding, grip, and tangential collision impulses.

> [!IMPORTANT]
> There is no recommended production preset yet. Existing packages, VPX imports, and new templates must use `Crr = 0` until a physical-table data set has been measured and validated. Do not choose a default from generic steel-on-steel coefficient tables.

## Where the value applies

A nonzero rolling-resistance value can be authored on a physics material asset or on the physics-material override for playfields, ramps, surfaces, primitives, rubbers, metal wire guides, hit targets, and drop targets.

The first implementation deliberately does not apply rolling resistance to:

- collision impacts;
- ball-to-ball contacts;
- the specialized flipper contact solver;
- spin about the contact normal, also called drilling or pivot friction; or
- a stationary ball on a shallow incline.

The model uses one constant coefficient. It does not vary with speed, load, temperature, or wear. Measurements may justify a speed-dependent follow-up, but that should not be assumed in advance.

Kinematic surface velocity is included when deciding whether the ball is rolling and which direction to damp. The v1 support load does not include kinematic surface acceleration, so the analytic guarantee applies to static surfaces and constant-velocity kinematic surfaces only.

## Mechanical reference

For the solid sphere used by VPE, ideal level rolling with a constant coefficient has deceleration:

```text
a_rr = 5/7 Crr g
```

For a ball rolling downhill on an incline of angle `theta`:

```text
a = 5/7 g (sin(theta) - Crr cos(theta))
```

The solver clamps at zero rolling speed and does not reverse the ball. It does not model the set-valued static state that could hold a motionless ball when `Crr >= tan(theta)`.

## Build a measurement fixture

Use a clean, standard pinball ball and a representative playfield sample. The sample should include the finish and preparation state that the table is expected to use, such as clear coat, wax, paint, Mylar, inserts, or representative wear.

Record these conditions before every measurement session:

- ball diameter and mass;
- ball condition and cleaning method;
- surface material, finish, and preparation state;
- surface angle and how it was measured;
- starting speed or release height;
- position versus time, preferably from high-frame-rate video or photogates;
- temperature; and
- whether the surface is waxed.

Keep the release repeatable and avoid imparting side spin. Measure enough of the trajectory to distinguish sustained rolling from the initial sliding-to-rolling transition. Discard a trial if the ball touches a guide, crosses a seam, or visibly slips during the fitted interval.

## Level-coast method

Measure the speed `v0` at the start of a confirmed rolling interval and the distance `d` from that point to rest. Use consistent units for distance and speed:

```text
Crr = 7 v0^2 / (10 g d)
```

Do not estimate `v0` only from the release height unless the ball is already rolling without slip at the start of the measured interval. A photogate pair or a fitted position-versus-time curve is preferred.

## Incline method

Measure the downhill acceleration `a` on a surface inclined by `theta`:

```text
Crr = (sin(theta) - 7 a / (5 g)) / cos(theta)
```

Choose an angle and starting speed that keep the ball moving downhill and rolling without slip throughout the fitted interval. Measure the actual surface angle rather than relying on a nominal cabinet setting.

## Data sheet

Keep raw observations as well as the derived coefficient. One row per trial is sufficient:

| Field | Value |
| --- | --- |
| Trial ID and timestamp | |
| Ball diameter and mass | |
| Ball preparation | |
| Surface and finish | |
| Waxed or unwaxed | |
| Temperature | |
| Surface angle | |
| Release method or height | |
| Starting speed `v0` | |
| Coast distance `d` | |
| Fitted acceleration `a` | |
| Derived `Crr` | |
| Video or photogate source | |
| Exclusions or observations | |

Preserve the position-versus-time samples or source video. A derived coefficient without the raw trajectory is not sufficient calibration evidence.

## Fit and validate a constant

1. Measure several starting speeds across the gameplay-relevant range.
2. Repeat every condition enough times to quantify trial-to-trial variation.
3. Fit one constant `Crr` to the retained trials and report the residuals.
4. Validate the fitted value on a second representative surface or preparation state.
5. Reproduce the fixture in VPE and compare position-versus-time or coast distance, not only the final stop time.
6. Record the Unity version, render-pipeline project, physics step settings, ball data, collider type, and material values used for the simulation.

If residuals show a repeatable speed dependence large enough to affect gameplay, keep the constant default at zero and propose a separate velocity-dependent model with tests and migration rules.

## Acceptance before recommending a preset

A proposed nonzero preset should include:

- the completed data sheet and raw trajectories;
- the fitted value and uncertainty or observed spread;
- results for more than one starting speed;
- validation on a second representative surface;
- a comparison between physical and simulated trajectories;
- an end-to-end table smoke in `VisualPinball.Unity.Project.Hdrp` using Unity 6000.5 or newer; and
- the known limitations listed on this page.

The headless core test project does not contain a render-pipeline implementation, so its table import/export tests cannot substitute for the HDRP smoke. The smoke should include real transformed and non-transformed colliders and confirm that mesh seams do not multiply rolling loss.

## Rollout policy

- Existing `.vpe` packages keep `Crr = 0` when the field is absent.
- Imported VPX physics materials and overrides start at `Crr = 0`.
- Existing tables do not change unless an author opts in.
- New templates stay at `Crr = 0` until the acceptance evidence above is published.
- A future recommended value must be presented as a measured preset for a documented surface, not as a universal material constant.

When tuning a table before a measured preset exists, keep rolling resistance at zero. Do not compensate by changing Friction: that also changes sliding, rubber behavior, and oblique impacts.
2 changes: 2 additions & 0 deletions VisualPinball.Unity/Documentation~/creators-guide/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
href: editor/unity-components.md
- name: Materials
href: editor/materials.md
- name: Calibrating Rolling Resistance
href: editor/rolling-resistance-calibration.md
- name: Asset Library Style Guide
href: editor/asset-library-styleguide.md
- name: Switch Manager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public abstract class TargetColliderInspector<TColliderComponent> : ColliderInsp
private SerializedProperty _elasticityProperty;
private SerializedProperty _elasticityFalloffProperty;
private SerializedProperty _frictionProperty;
private SerializedProperty _rollingResistanceProperty;
private SerializedProperty _scatterProperty;

protected override void OnEnable()
Expand All @@ -49,6 +50,8 @@ protected override void OnEnable()
_elasticityProperty = serializedObject.FindProperty(nameof(HitTargetColliderComponent.Elasticity));
_elasticityFalloffProperty = serializedObject.FindProperty(nameof(HitTargetColliderComponent.ElasticityFalloff));
_frictionProperty = serializedObject.FindProperty(nameof(HitTargetColliderComponent.Friction));
_rollingResistanceProperty = serializedObject.FindProperty(
nameof(HitTargetColliderComponent.RollingResistance));
_scatterProperty = serializedObject.FindProperty(nameof(HitTargetColliderComponent.Scatter));
}

Expand Down Expand Up @@ -88,6 +91,7 @@ public override void OnInspectorGUI()
PropertyField(_elasticityProperty);
PropertyField(_elasticityFalloffProperty);
PropertyField(_frictionProperty);
PropertyField(_rollingResistanceProperty);
PropertyField(_scatterProperty, "Scatter Angle");
EditorGUI.EndDisabledGroup();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class MetalWireGuideColliderInspector : ColliderInspector<MetalWireGuideD
private SerializedProperty _elasticityProperty;
private SerializedProperty _elasticityFalloffProperty;
private SerializedProperty _frictionProperty;
private SerializedProperty _rollingResistanceProperty;
private SerializedProperty _scatterProperty;

protected override void OnEnable()
Expand All @@ -46,6 +47,8 @@ protected override void OnEnable()
_elasticityProperty = serializedObject.FindProperty(nameof(MetalWireGuideColliderComponent.Elasticity));
_elasticityFalloffProperty = serializedObject.FindProperty(nameof(MetalWireGuideColliderComponent.ElasticityFalloff));
_frictionProperty = serializedObject.FindProperty(nameof(MetalWireGuideColliderComponent.Friction));
_rollingResistanceProperty = serializedObject.FindProperty(
nameof(MetalWireGuideColliderComponent.RollingResistance));
_scatterProperty = serializedObject.FindProperty(nameof(MetalWireGuideColliderComponent.Scatter));
}

Expand Down Expand Up @@ -75,6 +78,7 @@ public override void OnInspectorGUI()
PropertyField(_elasticityProperty);
PropertyField(_elasticityFalloffProperty);
PropertyField(_frictionProperty);
PropertyField(_rollingResistanceProperty);
PropertyField(_scatterProperty, "Scatter Angle");
EditorGUI.EndDisabledGroup();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class PhysicsMaterialInspector : UnityEditor.Editor
SerializedProperty ElasticityOverVelocity;
SerializedProperty Friction;
SerializedProperty FrictionOverVelocity;
SerializedProperty RollingResistance;
//SerializedProperty FrictionOverAngularMomentum;

SerializedProperty ScatterAngle;
Expand All @@ -53,6 +54,7 @@ private void OnEnable()
Friction = serializedObject.FindProperty(nameof(PhysicsMaterialAsset.Friction));
ScatterAngle = serializedObject.FindProperty(nameof(PhysicsMaterialAsset.ScatterAngle));
FrictionOverVelocity = serializedObject.FindProperty(nameof(PhysicsMaterialAsset.FrictionOverVelocity));
RollingResistance = serializedObject.FindProperty(nameof(PhysicsMaterialAsset.RollingResistance));
//FrictionOverAngularMomentum = serializedObject.FindProperty("FrictionOverAngularMomentum");
}

Expand Down Expand Up @@ -189,6 +191,10 @@ public override void OnInspectorGUI()
GUILayout.EndHorizontal();
*/

EditorGUI.indentLevel--;
GUILayout.Label("Rolling Resistance");
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(RollingResistance, true);
EditorGUI.indentLevel--;
EditorGUILayout.PropertyField(ScatterAngle, true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class PlayfieldColliderInspector : ColliderInspector<TableData, Playfield
private SerializedProperty _elasticityProperty;
private SerializedProperty _elasticityFalloffProperty;
private SerializedProperty _frictionProperty;
private SerializedProperty _rollingResistanceProperty;
private SerializedProperty _scatterProperty;
private SerializedProperty _gravityProperty;
private SerializedProperty _defaultScatterProperty;
Expand All @@ -40,6 +41,8 @@ protected override void OnEnable()
_elasticityProperty = serializedObject.FindProperty(nameof(PlayfieldColliderComponent.Elasticity));
_elasticityFalloffProperty = serializedObject.FindProperty(nameof(PlayfieldColliderComponent.ElasticityFalloff));
_frictionProperty = serializedObject.FindProperty(nameof(PlayfieldColliderComponent.Friction));
_rollingResistanceProperty = serializedObject.FindProperty(
nameof(PlayfieldColliderComponent.RollingResistance));
_scatterProperty = serializedObject.FindProperty(nameof(PlayfieldColliderComponent.Scatter));
_defaultScatterProperty = serializedObject.FindProperty(nameof(PlayfieldColliderComponent.DefaultScatter));
_collideWithBoundsProperty = serializedObject.FindProperty(nameof(PlayfieldColliderComponent.CollideWithBounds));
Expand All @@ -57,6 +60,7 @@ public override void OnInspectorGUI()

PropertyField(_gravityProperty, "Gravity Constant");
PropertyField(_frictionProperty, "Playfield Friction");
PropertyField(_rollingResistanceProperty, "Rolling Resistance");
PropertyField(_elasticityProperty, "Playfield Elasticity");
PropertyField(_elasticityFalloffProperty, "Playfield Elasticity Falloff");
PropertyField(_scatterProperty, "Playfield Scatter");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class PrimitiveColliderInspector : ColliderInspector<PrimitiveData, Primi
private SerializedProperty _elasticityProperty;
private SerializedProperty _elasticityFalloffProperty;
private SerializedProperty _frictionProperty;
private SerializedProperty _rollingResistanceProperty;
private SerializedProperty _scatterProperty;
private SerializedProperty _collisionReductionFactorProperty;
private SerializedProperty _overwritePhysicsProperty;
Expand All @@ -49,6 +50,8 @@ protected override void OnEnable()
_elasticityProperty = serializedObject.FindProperty(nameof(PrimitiveColliderComponent.Elasticity));
_elasticityFalloffProperty = serializedObject.FindProperty(nameof(PrimitiveColliderComponent.ElasticityFalloff));
_frictionProperty = serializedObject.FindProperty(nameof(PrimitiveColliderComponent.Friction));
_rollingResistanceProperty = serializedObject.FindProperty(
nameof(PrimitiveColliderComponent.RollingResistance));
_scatterProperty = serializedObject.FindProperty(nameof(PrimitiveColliderComponent.Scatter));
}

Expand Down Expand Up @@ -78,6 +81,7 @@ public override void OnInspectorGUI()
PropertyField(_elasticityProperty);
PropertyField(_elasticityFalloffProperty);
PropertyField(_frictionProperty);
PropertyField(_rollingResistanceProperty);
PropertyField(_scatterProperty, "Scatter Angle");
EditorGUI.EndDisabledGroup();
}
Expand Down
Loading
Loading