Skip to content

Commit

Permalink
fix: Fixed update interpolation on child entities (#2876)
Browse files Browse the repository at this point in the history
This PR fixes an issue where parenting a entity with fixed updates on would cause a drawing flicker, transform interpolation now is aware of changing parents so it interpolates drawing continuously to prevent any flickering


https://github.com/excaliburjs/Excalibur/assets/612071/4605bd0d-7a05-4e74-a75a-98cf6a701fa6
  • Loading branch information
eonarheim authored Jan 10, 2024
1 parent dca55c2 commit c329263
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Fixed

- Fixed issue where parenting a entity with fixed updates on would cause a drawing flicker, transform interpolation now is aware of changing parents so it interpolates drawing continuously to prevent any flickering
- `ex.Animation.reset()` did not properly reset all internal state

### Updates
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"browserstack": "karma start karma.conf.browsers.js",
"nuget": ".\\src\\tools\\NuGet.exe pack Excalibur.nuspec -OutputDirectory .\\build\\nuget -version",
"start": "npm run core:watch",
"start:esm": "npm run core:bundle:esm -- --watch",
"build": "npm run core",
"build:esm": "npm run core:bundle:esm",
"core": "npm run core:tsc && npm run core:copy && npm run core:bundle",
Expand Down
4 changes: 3 additions & 1 deletion src/engine/Collision/BodyComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,9 @@ export class BodyComponent extends Component<'ex.body'> implements Clonable<Body
public captureOldTransform() {
// Capture old values before integration step updates them
this.__oldTransformCaptured = true;
this.transform.get().clone(this.oldTransform);
const tx = this.transform.get();
tx.clone(this.oldTransform);
this.oldTransform.parent = tx.parent; // also grab parent
this.oldVel.setTo(this.vel.x, this.vel.y);
this.oldAcc.setTo(this.acc.x, this.acc.y);
}
Expand Down
39 changes: 38 additions & 1 deletion src/engine/Graphics/TransformInterpolation.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { Transform } from '../Math/transform';

/**
* Blend 2 transforms for interpolation
* Blend 2 transforms for interpolation, will interpolate from the context of newTx's parent if it exists
*/
export function blendTransform(oldTx: Transform, newTx: Transform, blend: number, target?: Transform): Transform {
if (oldTx.parent !== newTx.parent) {
// Caller expects a local transform
// Adjust old tx to be local to the new parent whatever that is
const oldTxWithNewParent = oldTx.clone();
const oldGlobalPos = oldTx.globalPos.clone();
const oldGlobalScale = oldTx.globalScale.clone();
const oldGlobalRotation = oldTx.globalRotation;
oldTxWithNewParent.parent = newTx.parent;
oldTxWithNewParent.globalPos = oldGlobalPos;
oldTxWithNewParent.globalScale = oldGlobalScale;
oldTxWithNewParent.globalRotation = oldGlobalRotation;
oldTx = oldTxWithNewParent;
}
let interpolatedPos = newTx.pos;
let interpolatedScale = newTx.scale;
let interpolatedRotation = newTx.rotation;
Expand All @@ -19,6 +32,30 @@ export function blendTransform(oldTx: Transform, newTx: Transform, blend: number
const sine = (1.0 - blend) * Math.sin(oldTx.rotation) + blend * Math.sin(newTx.rotation);
interpolatedRotation = Math.atan2(sine, cosine);

const tx = target ?? new Transform();
tx.setTransform(interpolatedPos, interpolatedRotation, interpolatedScale);
return tx;
}

/**
*
*/
export function blendGlobalTransform(oldTx: Transform, newTx: Transform, blend: number, target?: Transform): Transform {
let interpolatedPos = newTx.globalPos;
let interpolatedScale = newTx.globalScale;
let interpolatedRotation = newTx.globalRotation;

interpolatedPos = newTx.globalPos.scale(blend).add(
oldTx.globalPos.scale(1.0 - blend)
);
interpolatedScale = newTx.globalScale.scale(blend).add(
oldTx.globalScale.scale(1.0 - blend)
);
// Rotational lerp https://stackoverflow.com/a/30129248
const cosine = (1.0 - blend) * Math.cos(oldTx.globalRotation) + blend * Math.cos(newTx.globalRotation);
const sine = (1.0 - blend) * Math.sin(oldTx.globalRotation) + blend * Math.sin(newTx.globalRotation);
interpolatedRotation = Math.atan2(sine, cosine);

const tx = target ?? new Transform();
tx.setTransform(interpolatedPos, interpolatedRotation, interpolatedScale);
return tx;
Expand Down
7 changes: 6 additions & 1 deletion src/engine/Math/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class Transform {
get parent() {
return this._parent;
}
set parent(transform: Transform) {
set parent(transform: Transform | null) {
if (this._parent) {
const index = this._parent._children.indexOf(this);
if (index > -1) {
Expand Down Expand Up @@ -219,6 +219,11 @@ export class Transform {
this.flagDirty();
}

/**
* Clones the current transform
* **Warning does not clone the parent**
* @param dest
*/
public clone(dest?: Transform) {
const target = dest ?? new Transform();
this._pos.clone(target._pos);
Expand Down
9 changes: 5 additions & 4 deletions src/spec/GraphicsSystemSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ describe('A Graphics ECS System', () => {

actor.body.__oldTransformCaptured = true;

spyOn(game.graphicsContext, 'translate');
const translateSpy = spyOn(game.graphicsContext, 'translate');
spyOn(game.graphicsContext, 'rotate');
spyOn(game.graphicsContext, 'scale');

Expand All @@ -163,10 +163,11 @@ describe('A Graphics ECS System', () => {
graphicsSystem.preupdate();
graphicsSystem.notify(new ex.AddedEntity(actor));

game.currentFrameLagMs = 8; // current lag in a 30 fps frame
graphicsSystem.update([actor], 30);
game.currentFrameLagMs = (1000 / 30) / 2; // current lag in a 30 fps frame
graphicsSystem.update([actor], 16);

expect(game.graphicsContext.translate).toHaveBeenCalledWith(24, 24);
expect(translateSpy.calls.argsFor(0)).toEqual([10, 10]);
expect(translateSpy.calls.argsFor(1)).toEqual([45, 45]); // 45 because the parent offets by (-10, -10)
});

it('will not interpolate body graphics if disabled', async () => {
Expand Down
3 changes: 3 additions & 0 deletions wallaby.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ module.exports = function (wallaby) {
postprocessor: wallaby.postprocessors.webpack({
mode: 'none',
devtool: 'source-map',
optimization: {
providedExports: true,
},
resolve: {
extensions: ['.ts', '.js'],
alias: {
Expand Down

0 comments on commit c329263

Please sign in to comment.