Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: Refactor Transform & AffineMatrix + related perf improvements #2346

Merged
merged 29 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
da05e1d
Partially working web worker support
eonarheim May 28, 2022
1e98d77
Transform refactor + benchmark improvement!!!
eonarheim May 29, 2022
28df605
Working AffineMatrix
eonarheim May 30, 2022
0f9549d
Swap to AffineMatrix
eonarheim May 30, 2022
19495e9
Tweak performance
eonarheim May 30, 2022
78b94e3
Remove worker updates
eonarheim Jun 11, 2022
68afac1
Remove extras
eonarheim Jun 11, 2022
9dd160c
Undo changes
eonarheim Jun 11, 2022
9dfe112
Undo id change
eonarheim Jun 11, 2022
46c3fbe
Merge branch 'main' of https://github.com/excaliburjs/Excalibur into …
eonarheim Jun 11, 2022
24f50a1
Merge branch 'main' of https://github.com/excaliburjs/Excalibur into …
eonarheim Jun 11, 2022
51eeda0
Things are mostly working
eonarheim Jun 11, 2022
30b5116
Fix several tests
eonarheim Jun 11, 2022
b7dc54c
Fix rotation test
eonarheim Jun 11, 2022
c71cac8
Fix some more tests
eonarheim Jun 11, 2022
481f8d3
Tweak arcade solver near zero
eonarheim Jun 11, 2022
cee4957
Hedge against infinitesimal overlap
eonarheim Jun 11, 2022
3ad8460
Fix comment
eonarheim Jun 11, 2022
5e2f8ab
Tests pass!
eonarheim Jun 11, 2022
e4d1af7
Mostly working tweaked for performance
eonarheim Jun 12, 2022
0f7db8d
BoundingBox transform perf tweak + faster watch vector
eonarheim Jun 12, 2022
157ac02
Fix lint
eonarheim Jun 12, 2022
90cc069
Remove ex.Transform.posChanged$ for performance
eonarheim Jun 12, 2022
23b5a47
Fix lint
eonarheim Jun 13, 2022
a12ff36
Merge branch 'main' of https://github.com/excaliburjs/Excalibur into …
eonarheim Jun 13, 2022
f0a490f
Add tests and tweaks
eonarheim Jun 13, 2022
40b2175
Update integrator
eonarheim Jun 13, 2022
a227c2d
Performance tweak integrator
eonarheim Jun 13, 2022
f6f82e9
Add some tests
eonarheim Jun 13, 2022
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
16 changes: 14 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,21 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## Breaking Changes

- `ex.TransformComponent.posChanged$` has been removed, it incurs a steep performance cost
- `ex.EventDispatcher` meta events 'subscribe' and 'unsubscribe' were unused and undocumented and have been removed

### Deprecated

-

### Added
- Added new `ex.WatchVector` type that can observe changes to x/y more efficiently than `ex.watch()`
- Added performance improvements
* `ex.Vector.distance` improvement
* `ex.BoundingBox.transform` improvement
- Added ability to clone `ex.Vector.clone(destVector)` into a destination vector
- Added new `ex.Transform` type that is a light weight container for transformation data. This logic has been extracted from the `ex.TransformComponent`, this makes it easy to pass `ex.Transform`s around. Additionally the extracted `ex.Transform` logic has been refactored for performance.
- Added new `ex.AffineMatrix` that is meant for 2D affine transformations, it uses less memory and performs less calculations than the `ex.Matrix` which uses a 4x4 Float32 matrix.
- Added new fixed update step to Excalibur! This allows developers to configure a fixed FPS for the update loop. One advantage of setting a fix update is that you will have a more consistent and predictable physics simulation. Excalibur graphics will be interpolated automatically to avoid any jitter in the fixed update.
* If the fixed update FPS is greater than the display FPS, excalibur will run multiple updates in a row (at the configured update elapsed) to catch up, for example there could be X updates and 1 draw each clock step.
* If the fixed update FPS is less than the display FPS, excalibur will skip updates until it meets the desired FPS, for example there could be no update for 1 draw each clock step.
Expand Down Expand Up @@ -82,9 +90,12 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Add target element id to `ex.Screen.goFullScreen('some-element-id')` to influence the fullscreen element in the fullscreen browser API.

### Fixed

- Fixed issue where `ex.BoundingBox` overlap return false due to floating point rounding error causing multiple collisions to be evaluated sometimes
- Fixed issue with `ex.EventDispatcher` where removing a handler that didn't already exist would remove another handler by mistake
- Fixed issue with `ex.EventDispatcher` where concurrent modifications of the handler list where handlers would or would not fire correctly and throw
- Tweak to the `ex.ArcadeSolver` to produce more stable results by adjusting by an infinitesimal epsilon
- Contacts with overlap smaller than the epsilon are ignored
- Colliders with bounds that overlap smaller than the epsilon are ignored
- Fixed issue with `ex.ArcadeSolver` based collisions where colliders were catching on seams when sliding along a floor of multiple colliders. This was by sorting contacts by distance between bodies.
![sorted-collisions](https://user-images.githubusercontent.com/612071/172401390-9e9c3490-3566-47bf-b258-6a7da86a3464.gif)

Expand All @@ -99,7 +110,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Changed

-
- Most places where `ex.Matrix` was used have been switched to `ex.AffineMatrix`
- Most places where `ex.TransformComponent` was used have been switched to `ex.Transform`


## [0.26.0] - 2022-05-20
Expand Down
2 changes: 1 addition & 1 deletion sandbox/src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ game.on('fallbackgraphicscontext', (ctx) => {
console.log('fallback triggered', ctx);
});
//@ts-ignore For some reason ts doesn't like the /// slash import
const devtool = new ex.DevTools.DevTool(game);
// const devtool = new ex.DevTools.DevTool(game);


// var colorblind = new ex.ColorBlindnessPostProcessor(ex.ColorBlindnessMode.Deuteranope);
Expand Down
2 changes: 1 addition & 1 deletion sandbox/tests/side-collision/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Player2 extends ex.Actor {

onPostCollision(evt) {
if (evt.side === "Left" || evt.side === "Right") {
console.log(evt.side);
console.error(evt.side);
}

if (evt.side === ex.Side.Bottom) {
Expand Down
3 changes: 2 additions & 1 deletion src/engine/Actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { PointerEvents } from './Interfaces/PointerEventHandlers';
import { CollisionType } from './Collision/CollisionType';

import { Entity } from './EntityComponentSystem/Entity';
import { CoordPlane, TransformComponent } from './EntityComponentSystem/Components/TransformComponent';
import { TransformComponent } from './EntityComponentSystem/Components/TransformComponent';
import { MotionComponent } from './EntityComponentSystem/Components/MotionComponent';
import { GraphicsComponent } from './Graphics/GraphicsComponent';
import { Rectangle } from './Graphics/Rectangle';
Expand All @@ -43,6 +43,7 @@ import { PointerComponent } from './Input/PointerComponent';
import { ActionsComponent } from './Actions/ActionsComponent';
import { Raster } from './Graphics/Raster';
import { Text } from './Graphics/Text';
import { CoordPlane } from './Math/coord-plane';

/**
* Type guard for checking if something is an Actor
Expand Down
8 changes: 4 additions & 4 deletions src/engine/Camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { BoundingBox } from './Collision/BoundingBox';
import { Logger } from './Util/Log';
import { ExcaliburGraphicsContext } from './Graphics/Context/ExcaliburGraphicsContext';
import { watchAny } from './Util/Watch';
import { Matrix } from './Math/matrix';
import { AffineMatrix } from './Math/affine-matrix';

/**
* Interface that describes a custom camera strategy for tracking targets
Expand Down Expand Up @@ -239,8 +239,8 @@ export class LimitCameraBoundsStrategy implements CameraStrategy<BoundingBox> {
*
*/
export class Camera extends Class implements CanUpdate, CanInitialize {
public transform: Matrix = Matrix.identity();
public inverse: Matrix = Matrix.identity();
public transform: AffineMatrix = AffineMatrix.identity();
public inverse: AffineMatrix = AffineMatrix.identity();


protected _follow: Actor;
Expand Down Expand Up @@ -771,7 +771,7 @@ export class Camera extends Class implements CanUpdate, CanInitialize {
this.transform.reset();
this.transform.scale(this.zoom, this.zoom);
this.transform.translate(cameraPos.x, cameraPos.y);
this.transform.getAffineInverse(this.inverse);
this.transform.inverse(this.inverse);
}

private _isDoneShaking(): boolean {
Expand Down
16 changes: 10 additions & 6 deletions src/engine/Collision/BodyComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { EventDispatcher } from '../EventDispatcher';
import { createId, Id } from '../Id';
import { clamp } from '../Math/util';
import { ColliderComponent } from './ColliderComponent';
import { Matrix } from '../Math/matrix';
import { Transform } from '../Math/transform';

export interface BodyComponentOptions {
type?: CollisionType;
Expand All @@ -35,7 +35,7 @@ export class BodyComponent extends Component<'ex.body'> implements Clonable<Body
public readonly id: Id<'body'> = createId('body', BodyComponent._ID++);
public events = new EventDispatcher();

private _oldTransform = Matrix.identity();
private _oldTransform = new Transform();

/**
* Indicates whether the old transform has been captured at least once for interpolation
Expand All @@ -57,6 +57,10 @@ export class BodyComponent extends Component<'ex.body'> implements Clonable<Body
}
}

public get matrix() {
return this.transform.get().matrix;
}

/**
* Collision type for the rigidbody physics simulation, by default [[CollisionType.PreventCollision]]
*/
Expand Down Expand Up @@ -206,7 +210,7 @@ export class BodyComponent extends Component<'ex.body'> implements Clonable<Body
* The position of the actor last frame (x, y) in pixels
*/
public get oldPos(): Vector {
return this._oldTransform.getPosition();
return this._oldTransform.pos;
}

/**
Expand Down Expand Up @@ -257,7 +261,7 @@ export class BodyComponent extends Component<'ex.body'> implements Clonable<Body
* Gets/sets the rotation of the body from the last frame.
*/
public get oldRotation(): number {
return this._oldTransform.getRotation();
return this._oldTransform.rotation;
}

/**
Expand Down Expand Up @@ -286,7 +290,7 @@ export class BodyComponent extends Component<'ex.body'> implements Clonable<Body
* The scale of the actor last frame
*/
public get oldScale(): Vector {
return this._oldTransform.getScale();
return this._oldTransform.scale;
}

/**
Expand Down Expand Up @@ -383,7 +387,7 @@ 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.getGlobalMatrix().clone(this._oldTransform);
this.transform.get().clone(this._oldTransform);
this.oldVel.setTo(this.vel.x, this.vel.y);
this.oldAcc.setTo(this.acc.x, this.acc.y);
}
Expand Down
56 changes: 37 additions & 19 deletions src/engine/Collision/BoundingBox.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { vec, Vector } from '../Math/vector';
import { Vector } from '../Math/vector';
import { Ray } from '../Math/ray';
import { Color } from '../Color';
import { Side } from './Side';
import { ExcaliburGraphicsContext } from '../Graphics/Context/ExcaliburGraphicsContext';
import { Matrix } from '../Math/matrix';
import { AffineMatrix } from '../Math/affine-matrix';

export interface BoundingBoxOptions {
left: number;
Expand Down Expand Up @@ -161,24 +161,39 @@ export class BoundingBox {
* Transform the axis aligned bounding box by a [[Matrix]], producing a new axis aligned bounding box
* @param matrix
*/
public transform(matrix: Matrix) {
const matFirstColumn = vec(matrix.data[0], matrix.data[1]);
const xa = matFirstColumn.scale(this.left);
const xb = matFirstColumn.scale(this.right);

const matSecondColumn = vec(matrix.data[4], matrix.data[5]);
const ya = matSecondColumn.scale(this.top);
const yb = matSecondColumn.scale(this.bottom);
public transform(matrix: AffineMatrix) {
// inlined these calculations to not use vectors would speed it up slightly
// const matFirstColumn = vec(matrix.data[0], matrix.data[1]);
// const xa = matFirstColumn.scale(this.left);
const xa1 = matrix.data[0] * this.left;
const xa2 = matrix.data[1] * this.left;

// const xb = matFirstColumn.scale(this.right);
const xb1 = matrix.data[0] * this.right;
const xb2 = matrix.data[1] * this.right;

// const matSecondColumn = vec(matrix.data[2], matrix.data[3]);
// const ya = matSecondColumn.scale(this.top);
const ya1 = matrix.data[2] * this.top;
const ya2 = matrix.data[3] * this.top;

// const yb = matSecondColumn.scale(this.bottom);
const yb1 = matrix.data[2] * this.bottom;
const yb2 = matrix.data[3] * this.bottom;

const matrixPos = matrix.getPosition();
const topLeft = Vector.min(xa, xb).add(Vector.min(ya, yb)).add(matrixPos);
const bottomRight = Vector.max(xa, xb).add(Vector.max(ya, yb)).add(matrixPos);
// const topLeft = Vector.min(xa, xb).add(Vector.min(ya, yb)).add(matrixPos);
// const bottomRight = Vector.max(xa, xb).add(Vector.max(ya, yb)).add(matrixPos);
const left = Math.min(xa1, xb1) + Math.min(ya1, yb1) + matrixPos.x;
const top = Math.min(xa2, xb2) + Math.min(ya2, yb2) + matrixPos.y;
const right = Math.max(xa1, xb1) + Math.max(ya1, yb1) + matrixPos.x;
const bottom = Math.max(xa2, xb2) + Math.max(ya2, yb2) + matrixPos.y;

return new BoundingBox({
left: topLeft.x,
top: topLeft.y,
right: bottomRight.x,
bottom: bottomRight.y
left,//: topLeft.x,
top,//: topLeft.y,
right,//: bottomRight.x,
bottom//: bottomRight.y
});
}

Expand Down Expand Up @@ -292,17 +307,20 @@ export class BoundingBox {
/**
* Returns true if the bounding boxes overlap.
* @param other
* @param epsilon Optionally specify a small epsilon (default 0) as amount of overlap to ignore as overlap.
* This epsilon is useful in stable collision simulations.
*/
public overlaps(other: BoundingBox): boolean {
public overlaps(other: BoundingBox, epsilon?: number): boolean {
const e = epsilon || 0;
if (other.hasZeroDimensions()){
return this.contains(other);
}
if (this.hasZeroDimensions()) {
return other.contains(this);
}
const totalBoundingBox = this.combine(other);
return totalBoundingBox.width < other.width + this.width &&
totalBoundingBox.height < other.height + this.height;
return totalBoundingBox.width + e < other.width + this.width &&
totalBoundingBox.height + e < other.height + this.height;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/engine/Collision/ColliderComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class ColliderComponent extends Component<'ex.collider'> {
if (this._collider) {
this._collider.owner = this.owner;
if (tx) {
this._collider.update(tx);
this._collider.update(tx.get());
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/engine/Collision/Colliders/CircleCollider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { Color } from '../../Color';
import { Collider } from './Collider';

import { ClosestLineJumpTable } from './ClosestLineJumpTable';
import { Transform, TransformComponent } from '../../EntityComponentSystem';
import { ExcaliburGraphicsContext } from '../../Graphics/Context/ExcaliburGraphicsContext';
import { Transform } from '../../Math/transform';

export interface CircleColliderOptions {
/**
Expand All @@ -36,7 +36,7 @@ export class CircleCollider extends Collider {
public offset: Vector = Vector.Zero;

public get worldPos(): Vector {
const tx = this._transform as TransformComponent;
const tx = this._transform;
const scale = tx?.globalScale ?? Vector.One;
const rotation = tx?.globalRotation ?? 0;
const pos = (tx?.globalPos ?? Vector.Zero);
Expand All @@ -49,7 +49,7 @@ export class CircleCollider extends Collider {
* Get the radius of the circle
*/
public get radius(): number {
const tx = this._transform as TransformComponent;
const tx = this._transform;
const scale = tx?.globalScale ?? Vector.One;
// This is a trade off, the alternative is retooling circles to support ellipse collisions
return this._naturalRadius * Math.min(scale.x, scale.y);
Expand All @@ -59,7 +59,7 @@ export class CircleCollider extends Collider {
* Set the radius of the circle
*/
public set radius(val: number) {
const tx = this._transform as TransformComponent;
const tx = this._transform;
const scale = tx?.globalScale ?? Vector.One;
// This is a trade off, the alternative is retooling circles to support ellipse collisions
this._naturalRadius = val / Math.min(scale.x, scale.y);
Expand Down Expand Up @@ -87,7 +87,7 @@ export class CircleCollider extends Collider {
* Get the center of the collider in world coordinates
*/
public get center(): Vector {
const tx = this._transform as TransformComponent;
const tx = this._transform;
const scale = tx?.globalScale ?? Vector.One;
const rotation = tx?.globalRotation ?? 0;
const pos = (tx?.globalPos ?? Vector.Zero);
Expand Down Expand Up @@ -199,7 +199,7 @@ export class CircleCollider extends Collider {
* Get the axis aligned bounding box for the circle collider in world coordinates
*/
public get bounds(): BoundingBox {
const tx = this._transform as TransformComponent;
const tx = this._transform;
const scale = tx?.globalScale ?? Vector.One;
const rotation = tx?.globalRotation ?? 0;
const pos = (tx?.globalPos ?? Vector.Zero);
Expand Down Expand Up @@ -257,7 +257,7 @@ export class CircleCollider extends Collider {
}

public debug(ex: ExcaliburGraphicsContext, color: Color) {
const tx = this._transform as TransformComponent;
const tx = this._transform;
const scale = tx?.globalScale ?? Vector.One;
const rotation = tx?.globalRotation ?? 0;
const pos = (tx?.globalPos ?? Vector.Zero);
Expand Down
3 changes: 2 additions & 1 deletion src/engine/Collision/Colliders/Collider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { LineSegment } from '../../Math/line-segment';
import { Vector } from '../../Math/vector';
import { Ray } from '../../Math/ray';
import { Clonable } from '../../Interfaces/Clonable';
import { Entity, Transform } from '../../EntityComponentSystem';
import { Entity } from '../../EntityComponentSystem';
import { createId, Id } from '../../Id';
import { EventDispatcher } from '../../EventDispatcher';
import { ExcaliburGraphicsContext } from '../../Graphics/Context/ExcaliburGraphicsContext';
import { Transform } from '../../Math/transform';

/**
* A collision collider specifies the geometry that can detect when other collision colliders intersect
Expand Down
2 changes: 1 addition & 1 deletion src/engine/Collision/Colliders/CollisionJumpTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export const CollisionJumpTable = {
linePoly.owner = edge.owner;
const tx = edge.owner?.get(TransformComponent);
if (tx) {
linePoly.update(edge.owner.get(TransformComponent));
linePoly.update(edge.owner.get(TransformComponent).get());
}
// Gross hack but poly-poly works well
const contact = this.CollidePolygonPolygon(polygon, linePoly);
Expand Down
2 changes: 1 addition & 1 deletion src/engine/Collision/Colliders/CompositeCollider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Util } from '../..';
import { Pair } from '../Detection/Pair';
import { Color } from '../../Color';
import { Transform } from '../../EntityComponentSystem';
import { ExcaliburGraphicsContext } from '../../Graphics/Context/ExcaliburGraphicsContext';
import { LineSegment } from '../../Math/line-segment';
import { Projection } from '../../Math/projection';
Expand All @@ -12,6 +11,7 @@ import { CollisionContact } from '../Detection/CollisionContact';
import { DynamicTree } from '../Detection/DynamicTree';
import { DynamicTreeCollisionProcessor } from '../Detection/DynamicTreeCollisionProcessor';
import { Collider } from './Collider';
import { Transform } from '../../Math/transform';

export class CompositeCollider extends Collider {
private _transform: Transform;
Expand Down
Loading