Skip to content

Commit

Permalink
RuggeroVisintin/issue45 (#47)
Browse files Browse the repository at this point in the history
* feat(ecs): add .matchContainerTransform to allow component to match container transform

* docs(examples): update example with matching bounding box

* feat(ecs): add StaticObject entity

* fix(ecs): fix bounding box component not registered into static object

* docs(examples): update SimpleCollision example
RuggeroVisintin authored Dec 11, 2023
1 parent c633a7a commit 8dbe735
Showing 6 changed files with 139 additions and 33 deletions.
46 changes: 21 additions & 25 deletions examples/simpleCollision/index.html
Original file line number Diff line number Diff line change
@@ -13,48 +13,44 @@
const physx = new SparkEngine.Physx();
const physicsSystem = new SparkEngine.PhysicsSystem(physx);

const firstGameObject = new SparkEngine.GameObject();
firstGameObject.transform.size = { width: 20, height: 150 };
firstGameObject.material.diffuseColor = new SparkEngine.Rgb(255, 0, 0);
const firstGameObjectBB = new SparkEngine.BoundingBoxComponent();
firstGameObject.addComponent(firstGameObjectBB);
firstGameObjectBB.aabb = { ...firstGameObject.transform.position, ...firstGameObject.transform.size }

const secondGameObject = new SparkEngine.GameObject();
secondGameObject.transform.size = { width: 20, height: 150 };
secondGameObject.transform.position = { x: 280, y: 0 };
secondGameObject.material.diffuseColor = new SparkEngine.Rgb(0, 0, 255);

secondGameObjectBB = new SparkEngine.BoundingBoxComponent();
secondGameObject.addComponent(secondGameObjectBB);
secondGameObjectBB.aabb = { ...secondGameObject.transform.position, ...secondGameObject.transform.size }
const leftWall = new SparkEngine.StaticObject();
leftWall.transform.size = { width: 20, height: 150 };
leftWall.material.diffuseColor = new SparkEngine.Rgb(255, 0, 0);
leftWall.boundingBox.matchContainerTransform = true;

const rightWall = new SparkEngine.StaticObject();
rightWall.transform.size = { width: 20, height: 150 };
rightWall.transform.position = { x: 280, y: 0 };
rightWall.material.diffuseColor = new SparkEngine.Rgb(0, 0, 255);
rightWall.boundingBox.matchContainerTransform = true;

const pingPongBall = new SparkEngine.GameObject();
pingPongBall.transform.size = { width: 20, height: 20 };
pingPongBall.transform.position = { x: 140, y: 70 };
pingPongBall.material.diffuseColor = new SparkEngine.Rgb(0, 255, 0);
const pingPongBoundingBox = new SparkEngine.BoundingBoxComponent()
pingPongBall.addComponent(pingPongBoundingBox);
pingPongBoundingBox.aabb = { ...pingPongBall.transform.position, ...pingPongBall.transform.size }

renderSystem.registerComponent(firstGameObject.shape);
renderSystem.registerComponent(secondGameObject.shape);
const pingPongBB = new SparkEngine.BoundingBoxComponent()
pingPongBB.matchContainerTransform = true;

pingPongBall.addComponent(pingPongBB);

renderSystem.registerComponent(leftWall.shape);
renderSystem.registerComponent(rightWall.shape);
renderSystem.registerComponent(pingPongBall.shape);

physicsSystem.registerComponent(firstGameObjectBB);
physicsSystem.registerComponent(secondGameObjectBB);
physicsSystem.registerComponent(pingPongBoundingBox);
physicsSystem.registerComponent(leftWall.boundingBox);
physicsSystem.registerComponent(rightWall.boundingBox);
physicsSystem.registerComponent(pingPongBB);

let velocity = 5;

pingPongBoundingBox.onCollisionCb = () => {
pingPongBB.onCollisionCb = () => {
velocity *= -1;
pingPongBall.transform.position.x += velocity;
}

const drawFrame = () => {
pingPongBall.transform.position.x += velocity;
pingPongBoundingBox.aabb.x = pingPongBall.transform.position.x;

physicsSystem.update();
physx.simulate();
13 changes: 11 additions & 2 deletions src/ecs/components/BoundingBoxComponent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Type } from "../../core";
import { PhysicsObject, Physx } from "../../physx";
import { BaseComponent } from "./BaseComponent";
import { TransformComponent } from "./TransformComponent";
import { CollisionCallback, ICollidableComponent } from "./interfaces";

interface AABB {
@@ -12,11 +13,19 @@ interface AABB {

@Type('BoundingBoxComponent')
export class BoundingBoxComponent extends BaseComponent implements ICollidableComponent {
public onCollisionCb: CollisionCallback | undefined;
private defaultAABB: AABB = { x: 0, y: 0, width: 0, height: 0 };

public onCollisionCb: CollisionCallback | undefined;
public matchContainerTransform: boolean = false;

public get aabb(): AABB {
return this.defaultAABB;

const container = this.getContainer();
const containerTransform = container ? container.getComponent<TransformComponent>('TransformComponent') : undefined;

return this.matchContainerTransform && containerTransform
? { ...containerTransform.position, ...containerTransform.size }
: this.defaultAABB
}

public set aabb(value: AABB) {
12 changes: 12 additions & 0 deletions src/ecs/entities/StaticObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { BoundingBoxComponent } from "../components";
import { GameObject } from "./GameObject";

export class StaticObject extends GameObject {
public boundingBox = new BoundingBoxComponent();

constructor() {
super();

this.addComponent(this.boundingBox);
}
}
3 changes: 2 additions & 1 deletion src/ecs/entities/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './BaseEntity';
export * from './IEntity';
export * from './GameObject';
export * from './GameObject';
export * from './StaticObject';
73 changes: 68 additions & 5 deletions test/unit/ecs/components/BoundingBoxComponent.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BoundingBoxComponent, ICollidableComponent, Physx } from "../../../../src";
import { BaseEntity, BoundingBoxComponent, GameObject, ICollidableComponent, Physx } from "../../../../src";

describe('ecs/components/BoundingBoxComponent', () => {
let bbComponent = new BoundingBoxComponent();
@@ -9,6 +9,69 @@ describe('ecs/components/BoundingBoxComponent', () => {
physx = new Physx();
})

describe('get aabb()', () => {
it.each([
{
aabb: { x: 0, y: 0, width: 0, height: 0 },
expected: { x: 0, y: 0, width: 0, height: 0 }
},
{
aabb: { x: 5, y: 10, width: 15, height: 5 },
expected: { x: 5, y: 10, width: 15, height: 5 }
}
])('Should return the default aabb by default', (test) => {
bbComponent.aabb = test.aabb;
expect(bbComponent.aabb).toEqual(test.expected);
})

it('Should return the container entity AABB if .matchContainerTransform is true', () => {
const parentEntity = new GameObject();
parentEntity.addComponent(bbComponent);

bbComponent.matchContainerTransform = true;

parentEntity.transform.position = { x: 15, y: 25 };
parentEntity.transform.size = { width: 10, height: 10 };

expect(bbComponent.aabb).toEqual({
x: 15, y: 25, width: 10, height: 10
})
});

it('Should return the default AABB if .matchContainerTransform is true but parent container is null', () => {
bbComponent.matchContainerTransform = true;
bbComponent.aabb.x = 555;

expect(bbComponent.aabb).toEqual({
x: 555, y: 0, width: 0, height: 0
})
});

it('Should return the default AABB if .matchContainerTransform is false', () => {
const parentEntity = new GameObject();
parentEntity.addComponent(bbComponent);

bbComponent.matchContainerTransform = false;
bbComponent.aabb.x = 555;

expect(bbComponent.aabb).toEqual({
x: 555, y: 0, width: 0, height: 0
})
})

it('Should return the default AABB if .matchContainerTransform is true but parent container transform is null', () => {
const parentEntity = new BaseEntity();
parentEntity.addComponent(bbComponent);

bbComponent.matchContainerTransform = true;
bbComponent.aabb.x = 555;

expect(bbComponent.aabb).toEqual({
x: 555, y: 0, width: 0, height: 0
})
})
})

describe('.update()', () => {

it.each([{
@@ -24,15 +87,15 @@ describe('ecs/components/BoundingBoxComponent', () => {
expect(physx.physicalWorld).toEqual(expect.arrayContaining([{
object: {
aabb: test.expected
},
},
onCollisionCallback: expect.any(Function)
}]));
});


it('Should trigger the onCollision callback when a collision is registered', () => {
const cbBBComponentA = jest.fn(() => {});
const cbBBComponentB = jest.fn(() => {});
const cbBBComponentA = jest.fn(() => { });
const cbBBComponentB = jest.fn(() => { });

bbComponent.aabb.x = 5;
bbComponent.aabb.y = 5;
@@ -53,5 +116,5 @@ describe('ecs/components/BoundingBoxComponent', () => {
expect(cbBBComponentA).toHaveBeenCalled();
expect(cbBBComponentB).toHaveBeenCalled();
})
})
});
})
25 changes: 25 additions & 0 deletions test/unit/ecs/entities/StaticObject.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { BoundingBoxComponent, GameObject, StaticObject } from "../../../../src"

describe('ecs/entities/StaticObject', () => {
let staticObject = new StaticObject();

afterEach(() => {
staticObject = new StaticObject();
})

describe('.constructor()', () => {
it('Should extend GameObject class', () => {
expect(staticObject).toBeInstanceOf(GameObject);
})

it('Should have a bounding box by default', () => {
expect(staticObject.boundingBox).toEqual(expect.objectContaining({
aabb: { x: 0, y: 0, width: 0, height: 0 },
}));
})

it('Should register this entity as a container of its default components', () => {
expect(staticObject.boundingBox.getContainer()).toBe(staticObject);
})
})
})

0 comments on commit 8dbe735

Please sign in to comment.