From: Rubén Beltrán del Río Date: Sat, 21 Apr 2018 22:45:38 +0000 (-0500) Subject: Physics Engine (#4) X-Git-Url: https://git.r.bdr.sh/rbdr/sumo/commitdiff_plain/493ec31cb19b4211c703762d14a4e6232c4c2143?hp=-c Physics Engine (#4) * Add matter-js as a dependency * Add body component * Fix renderable node doc * Add the physics node type * Correct typo in physics enabled node types * Update docs on render system * Add physics system * Update changelog * Add physics engine to main app * Tweak the gravity of the world * Add body to sumo factory * Add angle component * Add angle to entity * Add angle to the nodes * Pass angle between systems * Set default angle to 0 * Split systems and nodes * Fix indentaion in physics world control * Add third sumo entity * Adjust values for constriants and physics * Correct docs in new nodes * Update built-in docs --- 493ec31cb19b4211c703762d14a4e6232c4c2143 diff --git a/CHANGELOG.md b/CHANGELOG.md index d5e5ad3..5fc6da3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,5 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - This CHANGELOG - A README - A CONTRIBUTING guide +- Pixi powered renderer system +- Matter-js powered physics system [Unreleased]: https://github.com/rbdr/sumo/compare/master...develop diff --git a/doc/README.md b/doc/README.md index 2d79f30..2d367a5 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,3 +1,268 @@ +## Classes + +
+
AngleComponent ⇐ external:Serpentity.Component
+
+
BodyComponent ⇐ external:Serpentity.Component
+
+
CoupledEntitiesComponent ⇐ external:Serpentity.Component
+
+
PixiContainerComponent ⇐ external:Serpentity.Component
+
+
PhysicalWithAttributesNode ⇐ external:Serpentity.Node
+
+
PhysicalNode ⇐ external:Serpentity.Node
+
+
RenderableCoupleNode ⇐ external:Serpentity.Node
+
+
RenderableWithAttributesNode ⇐ external:Serpentity.Node
+
+
RenderableNode ⇐ external:Serpentity.Node
+
+
Sumo
+
+
AttributesToRenderableSystem ⇐ external:Serpentity.System
+
+
CreateCouplingLineSystem ⇐ external:Serpentity.System
+
+
PhysicsToAttributesSystem ⇐ external:Serpentity.System
+
+
PhysicsWorldControlSystem ⇐ external:Serpentity.System
+
+
RenderSystem ⇐ external:Serpentity.System
+
+
+ +## Members + +
+
PixiFactory : object
+

Factory object that contains many methods to create prefab pixi +objects

+
+
SumoFactory : object
+

Factory object that contains many methods to create prefab entities.

+
+
+ + + +## AngleComponent ⇐ external:Serpentity.Component +**Kind**: global class +**Extends**: external:Serpentity.Component + + +### new AngleComponent(config) +Component that stores an angle + + +| Param | Type | Description | +| --- | --- | --- | +| config | object | a configuration object to extend. | + + + +## BodyComponent ⇐ external:Serpentity.Component +**Kind**: global class +**Extends**: external:Serpentity.Component + + +### new BodyComponent(config) +Component that stores a body for physics calculation + + +| Param | Type | Description | +| --- | --- | --- | +| config | object | a configuration object to extend. | + + + +## CoupledEntitiesComponent ⇐ external:Serpentity.Component +**Kind**: global class +**Extends**: external:Serpentity.Component + + +### new CoupledEntitiesComponent(config) +Component that stores a number of entities coupled to this one. + + +| Param | Type | Description | +| --- | --- | --- | +| config | object | a configuration object to extend. | + + + +## PixiContainerComponent ⇐ external:Serpentity.Component +**Kind**: global class +**Extends**: external:Serpentity.Component + +* [PixiContainerComponent](#PixiContainerComponent) ⇐ external:Serpentity.Component + * [new PixiContainerComponent(config)](#new_PixiContainerComponent_new) + * [.container](#PixiContainerComponent+container) + + + +### new PixiContainerComponent(config) +Component that stores a pixi container + + +| Param | Type | Description | +| --- | --- | --- | +| config | object | a configuration object to extend. | + + + +### pixiContainerComponent.container +The properthy that holds the pixi container + +**Kind**: instance property of [PixiContainerComponent](#PixiContainerComponent) +**Properties** + +| Name | Type | +| --- | --- | +| container | external:PixiJs.Container | + + + +## PhysicalWithAttributesNode ⇐ external:Serpentity.Node +**Kind**: global class +**Extends**: external:Serpentity.Node + +* [PhysicalWithAttributesNode](#PhysicalWithAttributesNode) ⇐ external:Serpentity.Node + * [new PhysicalWithAttributesNode()](#new_PhysicalWithAttributesNode_new) + * [.types](#PhysicalWithAttributesNode.types) + + + +### new PhysicalWithAttributesNode() +Node identifying an entity that can interact with physics and has +components updated from its values + + + +### PhysicalWithAttributesNode.types +Holds the types that are used to identify an entity with a physics +object and external attributes + +**Kind**: static property of [PhysicalWithAttributesNode](#PhysicalWithAttributesNode) +**Properties** + +| Name | Type | +| --- | --- | +| types | object | + + + +## PhysicalNode ⇐ external:Serpentity.Node +**Kind**: global class +**Extends**: external:Serpentity.Node + +* [PhysicalNode](#PhysicalNode) ⇐ external:Serpentity.Node + * [new PhysicalNode()](#new_PhysicalNode_new) + * [.types](#PhysicalNode.types) + + + +### new PhysicalNode() +Node identifying an entity that can interact with physics + + + +### PhysicalNode.types +Holds the types that are used to identify an entity with a physical +body + +**Kind**: static property of [PhysicalNode](#PhysicalNode) +**Properties** + +| Name | Type | +| --- | --- | +| types | object | + + + +## RenderableCoupleNode ⇐ external:Serpentity.Node +**Kind**: global class +**Extends**: external:Serpentity.Node + +* [RenderableCoupleNode](#RenderableCoupleNode) ⇐ external:Serpentity.Node + * [new RenderableCoupleNode()](#new_RenderableCoupleNode_new) + * [.types](#RenderableCoupleNode.types) + + + +### new RenderableCoupleNode() +Node identifying an entity that has coupled entities and can be +rendered + + + +### RenderableCoupleNode.types +Holds the types that are used to identify a renderable couple + +**Kind**: static property of [RenderableCoupleNode](#RenderableCoupleNode) +**Properties** + +| Name | Type | +| --- | --- | +| types | object | + + + +## RenderableWithAttributesNode ⇐ external:Serpentity.Node +**Kind**: global class +**Extends**: external:Serpentity.Node + +* [RenderableWithAttributesNode](#RenderableWithAttributesNode) ⇐ external:Serpentity.Node + * [new RenderableWithAttributesNode()](#new_RenderableWithAttributesNode_new) + * [.types](#RenderableWithAttributesNode.types) + + + +### new RenderableWithAttributesNode() +Node identifying a renderable entity, should have position and a +symbol to render + + + +### RenderableWithAttributesNode.types +Holds the types that are used to identify a renderable with external +attributes + +**Kind**: static property of [RenderableWithAttributesNode](#RenderableWithAttributesNode) +**Properties** + +| Name | Type | +| --- | --- | +| types | object | + + + +## RenderableNode ⇐ external:Serpentity.Node +**Kind**: global class +**Extends**: external:Serpentity.Node + +* [RenderableNode](#RenderableNode) ⇐ external:Serpentity.Node + * [new RenderableNode()](#new_RenderableNode_new) + * [.types](#RenderableNode.types) + + + +### new RenderableNode() +Node identifying a renderable entity, should have a pixi renderable + + + +### RenderableNode.types +Holds the types that are used to identify a renderable entity + +**Kind**: static property of [RenderableNode](#RenderableNode) +**Properties** + +| Name | Type | +| --- | --- | +| types | object | + ## Sumo @@ -40,3 +305,322 @@ live until after you stop and start the loop) Pauses the loop **Kind**: instance method of [Sumo](#Sumo) + + +## AttributesToRenderableSystem ⇐ external:Serpentity.System +**Kind**: global class +**Extends**: external:Serpentity.System + +* [AttributesToRenderableSystem](#AttributesToRenderableSystem) ⇐ external:Serpentity.System + * [new AttributesToRenderableSystem(config)](#new_AttributesToRenderableSystem_new) + * [.added(engine)](#AttributesToRenderableSystem+added) + * [.removed()](#AttributesToRenderableSystem+removed) + * [.update(currentFrameDuration)](#AttributesToRenderableSystem+update) + + + +### new AttributesToRenderableSystem(config) +Updates the renderables based on their attribuets + + +| Param | Type | Description | +| --- | --- | --- | +| config | object | a configuration object to extend. | + + + +### attributesToRenderableSystem.added(engine) +Initializes system when added. Requests renderable nodes and +attaches to event listeners to add / remove them to pixi stage + +**Kind**: instance method of [AttributesToRenderableSystem](#AttributesToRenderableSystem) + +| Param | Type | Description | +| --- | --- | --- | +| engine | external:Serpentity.Engine | the serpentity engine to which we are getting added | + + + +### attributesToRenderableSystem.removed() +Clears system resources when removed. + +**Kind**: instance method of [AttributesToRenderableSystem](#AttributesToRenderableSystem) + + +### attributesToRenderableSystem.update(currentFrameDuration) +Runs on every update of the loop. Updates the graphics so they're +rendered correctly + +**Kind**: instance method of [AttributesToRenderableSystem](#AttributesToRenderableSystem) + +| Param | Type | Description | +| --- | --- | --- | +| currentFrameDuration | Number | the duration of the current frame | + + + +## CreateCouplingLineSystem ⇐ external:Serpentity.System +**Kind**: global class +**Extends**: external:Serpentity.System + +* [CreateCouplingLineSystem](#CreateCouplingLineSystem) ⇐ external:Serpentity.System + * [new CreateCouplingLineSystem(config)](#new_CreateCouplingLineSystem_new) + * [.added(engine)](#CreateCouplingLineSystem+added) + * [.removed()](#CreateCouplingLineSystem+removed) + * [.update(currentFrameDuration)](#CreateCouplingLineSystem+update) + + + +### new CreateCouplingLineSystem(config) +Renders renderable objects using pixi + + +| Param | Type | Description | +| --- | --- | --- | +| config | object | a configuration object to extend. | + + + +### createCouplingLineSystem.added(engine) +Initializes system when added. Requests renderable nodes and + +**Kind**: instance method of [CreateCouplingLineSystem](#CreateCouplingLineSystem) + +| Param | Type | Description | +| --- | --- | --- | +| engine | external:Serpentity.Engine | the serpentity engine to which we are getting added | + + + +### createCouplingLineSystem.removed() +Clears system resources when removed. + +**Kind**: instance method of [CreateCouplingLineSystem](#CreateCouplingLineSystem) + + +### createCouplingLineSystem.update(currentFrameDuration) +Runs on every update of the loop. Does nothing. + +**Kind**: instance method of [CreateCouplingLineSystem](#CreateCouplingLineSystem) + +| Param | Type | Description | +| --- | --- | --- | +| currentFrameDuration | Number | the duration of the current frame | + + + +## PhysicsToAttributesSystem ⇐ external:Serpentity.System +**Kind**: global class +**Extends**: external:Serpentity.System + +* [PhysicsToAttributesSystem](#PhysicsToAttributesSystem) ⇐ external:Serpentity.System + * [new PhysicsToAttributesSystem(config)](#new_PhysicsToAttributesSystem_new) + * [.added(engine)](#PhysicsToAttributesSystem+added) + * [.removed()](#PhysicsToAttributesSystem+removed) + * [.update(currentFrameDuration)](#PhysicsToAttributesSystem+update) + + + +### new PhysicsToAttributesSystem(config) +Distribuets physics data to the related components + + +| Param | Type | Description | +| --- | --- | --- | +| config | object | a configuration object to extend. | + + + +### physicsToAttributesSystem.added(engine) +Initializes system when added. Requests physics nodes + +**Kind**: instance method of [PhysicsToAttributesSystem](#PhysicsToAttributesSystem) + +| Param | Type | Description | +| --- | --- | --- | +| engine | external:Serpentity.Engine | the serpentity engine to which we are getting added | + + + +### physicsToAttributesSystem.removed() +Clears system resources when removed. + +**Kind**: instance method of [PhysicsToAttributesSystem](#PhysicsToAttributesSystem) + + +### physicsToAttributesSystem.update(currentFrameDuration) +Runs on every update of the loop. Updates the other components +based on physics + +**Kind**: instance method of [PhysicsToAttributesSystem](#PhysicsToAttributesSystem) + +| Param | Type | Description | +| --- | --- | --- | +| currentFrameDuration | Number | the duration of the current frame | + + + +## PhysicsWorldControlSystem ⇐ external:Serpentity.System +**Kind**: global class +**Extends**: external:Serpentity.System + +* [PhysicsWorldControlSystem](#PhysicsWorldControlSystem) ⇐ external:Serpentity.System + * [new PhysicsWorldControlSystem(config)](#new_PhysicsWorldControlSystem_new) + * [.added(engine)](#PhysicsWorldControlSystem+added) + * [.removed()](#PhysicsWorldControlSystem+removed) + * [.update(currentFrameDuration)](#PhysicsWorldControlSystem+update) + + + +### new PhysicsWorldControlSystem(config) +Adds and removes objects to the physics world and calls update + + +| Param | Type | Description | +| --- | --- | --- | +| config | object | a configuration object to extend. | + + + +### physicsWorldControlSystem.added(engine) +Initializes system when added. Requests physics nodes and +attaches to event listeners to add / remove them to pixi stage + +**Kind**: instance method of [PhysicsWorldControlSystem](#PhysicsWorldControlSystem) + +| Param | Type | Description | +| --- | --- | --- | +| engine | external:Serpentity.Engine | the serpentity engine to which we are getting added | + + + +### physicsWorldControlSystem.removed() +Clears system resources when removed. + +**Kind**: instance method of [PhysicsWorldControlSystem](#PhysicsWorldControlSystem) + + +### physicsWorldControlSystem.update(currentFrameDuration) +Runs on every update of the loop. Updates the physics + +**Kind**: instance method of [PhysicsWorldControlSystem](#PhysicsWorldControlSystem) + +| Param | Type | Description | +| --- | --- | --- | +| currentFrameDuration | Number | the duration of the current frame | + + + +## RenderSystem ⇐ external:Serpentity.System +**Kind**: global class +**Extends**: external:Serpentity.System + +* [RenderSystem](#RenderSystem) ⇐ external:Serpentity.System + * [new RenderSystem(config)](#new_RenderSystem_new) + * [.added(engine)](#RenderSystem+added) + * [.removed()](#RenderSystem+removed) + * [.update(currentFrameDuration)](#RenderSystem+update) + + + +### new RenderSystem(config) +Renders renderable objects using pixi + + +| Param | Type | Description | +| --- | --- | --- | +| config | object | a configuration object to extend. | + + + +### renderSystem.added(engine) +Initializes system when added. Requests renderable nodes and +attaches to event listeners to add / remove them to pixi stage + +**Kind**: instance method of [RenderSystem](#RenderSystem) + +| Param | Type | Description | +| --- | --- | --- | +| engine | external:Serpentity.Engine | the serpentity engine to which we are getting added | + + + +### renderSystem.removed() +Clears system resources when removed. + +**Kind**: instance method of [RenderSystem](#RenderSystem) + + +### renderSystem.update(currentFrameDuration) +Runs on every update of the loop. Does nothing. + +**Kind**: instance method of [RenderSystem](#RenderSystem) + +| Param | Type | Description | +| --- | --- | --- | +| currentFrameDuration | Number | the duration of the current frame | + + + +## PixiFactory : object +Factory object that contains many methods to create prefab pixi +objects + +**Kind**: global variable + +* [PixiFactory](#PixiFactory) : object + * [.createSumo()](#PixiFactory.createSumo) ⇒ external:CreateJs.Container + * [.createEmptyGraphic()](#PixiFactory.createEmptyGraphic) ⇒ external:CreateJs.Container + + + +### PixiFactory.createSumo() ⇒ external:CreateJs.Container +Creates a sumo container + +**Kind**: static method of [PixiFactory](#PixiFactory) +**Returns**: external:CreateJs.Container - the created container + + +### PixiFactory.createEmptyGraphic() ⇒ external:CreateJs.Container +Creates an empty graphic + +**Kind**: static method of [PixiFactory](#PixiFactory) +**Returns**: external:CreateJs.Container - the created container + + +## SumoFactory : object +Factory object that contains many methods to create prefab entities. + +**Kind**: global variable + +* [SumoFactory](#SumoFactory) : object + * [.createSumo([engine], [config])](#SumoFactory.createSumo) ⇒ external:Serpentity.Entity + * [.createRubberBand([engine], [config])](#SumoFactory.createRubberBand) ⇒ external:Serpentity.Entity + + + +### SumoFactory.createSumo([engine], [config]) ⇒ external:Serpentity.Entity +Creates a sumo entity and adds it to the engine. Can override +position in the config object + +**Kind**: static method of [SumoFactory](#SumoFactory) +**Returns**: external:Serpentity.Entity - the created entity + +| Param | Type | Description | +| --- | --- | --- | +| [engine] | external:Serpentity | the serpentity engine to attach to. If not sent, it will not be attached. | +| [config] | object | the config to override the entity, accepts the key `position` as an object with an x and y property. | + + + +### SumoFactory.createRubberBand([engine], [config]) ⇒ external:Serpentity.Entity +Creates a rubber band entity and adds it to the engine. + +**Kind**: static method of [SumoFactory](#SumoFactory) +**Returns**: external:Serpentity.Entity - the created entity + +| Param | Type | Description | +| --- | --- | --- | +| [engine] | external:Serpentity | the serpentity engine to attach to. If not sent, it will not be attached. | +| [config] | object | the config to override the entity, it must include entityA and entityB which will be tied by it. If they are not sent or don't have a physics body, this will throw. | + diff --git a/lib/components/angle.js b/lib/components/angle.js new file mode 100644 index 0000000..4de9764 --- /dev/null +++ b/lib/components/angle.js @@ -0,0 +1,25 @@ +import { Component } from '@serpentity/serpentity'; + +/** + * Component that stores an angle + * + * @extends {external:Serpentity.Component} + * @class AngleComponent + * @param {object} config a configuration object to extend. + */ +export default class AngleComponent extends Component { + constructor(config) { + + super(config); + + /** + * The properthy that holds the pixi container + * + * @property {external:MatterJs.Angle} angle + * @instance + * @memberof AngleComponent + */ + this.angle = this.angle || 0; + } +}; + diff --git a/lib/components/body.js b/lib/components/body.js new file mode 100644 index 0000000..7438dc3 --- /dev/null +++ b/lib/components/body.js @@ -0,0 +1,25 @@ +import { Component } from '@serpentity/serpentity'; + +/** + * Component that stores a body for physics calculation + * + * @extends {external:Serpentity.Component} + * @class BodyComponent + * @param {object} config a configuration object to extend. + */ +export default class BodyComponent extends Component { + constructor(config) { + + super(config); + + /** + * The properthy that holds the pixi container + * + * @property {external:MatterJs.Body} body + * @instance + * @memberof BodyComponent + */ + this.body = this.body || null; + } +}; + diff --git a/lib/components/coupled_entities.js b/lib/components/coupled_entities.js new file mode 100644 index 0000000..29db813 --- /dev/null +++ b/lib/components/coupled_entities.js @@ -0,0 +1,25 @@ +import { Component } from '@serpentity/serpentity'; + +/** + * Component that stores a number of entities coupled to this one. + * + * @extends {external:Serpentity.Component} + * @class CoupledEntitiesComponent + * @param {object} config a configuration object to extend. + */ +export default class CoupledEntitiesComponent extends Component { + constructor(config) { + + super(config); + + /** + * An array of coupled entities + * + * @property {Array} coupledEntities + * @instance + * @memberof CoupledEntitiesComponent + */ + this.coupledEntities = this.coupledEntities || []; + } +}; + diff --git a/lib/factories/pixi.js b/lib/factories/pixi.js index e3c1b5a..851a074 100644 --- a/lib/factories/pixi.js +++ b/lib/factories/pixi.js @@ -16,9 +16,9 @@ export default { * @memberof PixiFactory * @return {external:CreateJs.Container} the created container */ - createSumo() { + createSumo(config) { - const radius = 25; + const radius = config.radius; // The body const body = new Graphics(); @@ -61,6 +61,18 @@ export default { body.addChild(rightPupil); return body; + }, + + /** + * Creates an empty graphic + * + * @function createEmptyGraphic + * @memberof PixiFactory + * @return {external:CreateJs.Container} the created container + */ + createEmptyGraphic(config) { + + return new Graphics(); } }; diff --git a/lib/factories/sumo.js b/lib/factories/sumo.js index b0e5416..d1f31f0 100644 --- a/lib/factories/sumo.js +++ b/lib/factories/sumo.js @@ -1,9 +1,18 @@ +import { Bodies, Constraint } from 'matter-js'; import { Entity } from '@serpentity/serpentity'; +import AngleComponent from '../components/angle'; +import BodyComponent from '../components/body'; +import CoupledEntitiesComponent from '../components/coupled_entities'; import PositionComponent from '@serpentity/components.position'; import PixiContainerComponent from '../components/pixi_container'; import PixiFactory from '../factories/pixi'; +const internals = { + kNoEntityError: 'Entity Not Found: This method requires entityA and entityB to be set in the config.', + kEntityHasNoBodyError: 'Entity Has No Body: This method requires entities have a BodyComponent.' +}; + /** * Factory object that contains many methods to create prefab entities. * @@ -28,20 +37,103 @@ export default { const entity = new Entity(); + // POSITION + entity.addComponent(new PositionComponent(config.position)); + const position = entity.getComponent(PositionComponent); + + entity.addComponent(new AngleComponent(config.angle)); + const angle = entity.getComponent(AngleComponent); + + // RENDERING + + const radius = 25; const container = config.container || { - container: PixiFactory.createSumo() + container: PixiFactory.createSumo({ radius }) }; - - // Match the symbol position with the entity position on init - // Otherwise it's up to systems - const position = entity.getComponent(PositionComponent); container.container.position.x = position.x; container.container.position.y = position.y; + container.container.rotation = angle.angle; + entity.addComponent(new PixiContainerComponent(container)); + + // PHYSICS + + const frictionAir = 0.00001; + const friction = 0.00001; + const restitution = 0.9; + const density = 0.5; + + const body = Bodies.circle(position.x, position.y, radius, { + angle: angle.angle, + density, + frictionAir, + friction, + restitution + }); + entity.addComponent(new BodyComponent({ body })); + + if (engine) { + engine.addEntity(entity); + } + + return entity; + }, + + /** + * Creates a rubber band entity and adds it to the engine. + * + * @function createRubberBand + * @memberof SumoFactory + * @param {external:Serpentity} [engine] the serpentity engine to attach + * to. If not sent, it will not be attached. + * @param {object} [config] the config to override the entity, it + * must include entityA and entityB which will be tied by it. If they + * are not sent or don't have a physics body, this will throw. + * @return {external:Serpentity.Entity} the created entity + */ + createRubberBand(engine, config = {}) { + + const entity = new Entity(); + + if (!config.entityA || !config.entityB) { + throw new Error(internals.kNoEntityError); + } + + if (!config.entityA.hasComponent(BodyComponent) || !config.entityB.hasComponent(BodyComponent)) { + throw new Error(internals.kEntityHasNoBodyError); + } + // RENDERING + + const container = config.container || { + container: PixiFactory.createEmptyGraphic() + }; entity.addComponent(new PixiContainerComponent(container)); + // PHYSICS + + const bodyA = config.entityA.getComponent(BodyComponent).body; + const bodyB = config.entityB.getComponent(BodyComponent).body; + const damping = 0.01; + const length = 100; + const stiffness = 0.0001; + const angularStiffness = 0.0001; + + const body = Constraint.create({ + bodyA, + bodyB, + damping, + length, + stiffness, + angularStiffness + }); + entity.addComponent(new BodyComponent({ body })); + + entity.addComponent(new CoupledEntitiesComponent({ + coupledEntities: [config.entityA, config.entityB] + })); + if (engine) { engine.addEntity(entity); } diff --git a/lib/nodes/physical.js b/lib/nodes/physical.js new file mode 100644 index 0000000..77139ec --- /dev/null +++ b/lib/nodes/physical.js @@ -0,0 +1,26 @@ +import { Node } from '@serpentity/serpentity'; + +import BodyComponent from '../components/body'; + +/** + * Node identifying an entity that can interact with physics + * + * @extends {external:Serpentity.Node} + * @class PhysicalNode + */ +export default class PhysicalNode extends Node { + +}; + +/** + * Holds the types that are used to identify an entity with a physical + * body + * + * @property {object} types + * @name types + * @memberof PhysicalNode + */ +PhysicalNode.types = { + body: BodyComponent +}; + diff --git a/lib/nodes/physical_with_attributes.js b/lib/nodes/physical_with_attributes.js new file mode 100644 index 0000000..96ba79d --- /dev/null +++ b/lib/nodes/physical_with_attributes.js @@ -0,0 +1,31 @@ +import { Node } from '@serpentity/serpentity'; + +import AngleComponent from '../components/angle'; +import BodyComponent from '../components/body'; +import PositionComponent from '@serpentity/components.position'; + +/** + * Node identifying an entity that can interact with physics and has + * components updated from its values + * + * @extends {external:Serpentity.Node} + * @class PhysicalWithAttributesNode + */ +export default class PhysicalWithAttributesNode extends Node { + +}; + +/** + * Holds the types that are used to identify an entity with a physics + * object and external attributes + * + * @property {object} types + * @name types + * @memberof PhysicalWithAttributesNode + */ +PhysicalWithAttributesNode.types = { + angle: AngleComponent, + position: PositionComponent, + body: BodyComponent +}; + diff --git a/lib/nodes/renderable.js b/lib/nodes/renderable.js index 0a3a111..12227a3 100644 --- a/lib/nodes/renderable.js +++ b/lib/nodes/renderable.js @@ -1,11 +1,9 @@ import { Node } from '@serpentity/serpentity'; import PixiContainerComponent from '../components/pixi_container'; -import PositionComponent from '@serpentity/components.position'; /** - * Node identifying a renderable entity, should have position and a - * symbol to render + * Node identifying a renderable entity, should have a pixi renderable * * @extends {external:Serpentity.Node} * @class RenderableNode @@ -15,13 +13,12 @@ export default class RenderableNode extends Node { }; /** - * Holds the types that are used to identify a wave entity + * Holds the types that are used to identify a renderable entity * * @property {object} types * @name types - * @memberof WaveNode + * @memberof RenderableNode */ RenderableNode.types = { - position: PositionComponent, container: PixiContainerComponent }; diff --git a/lib/nodes/renderable_couple.js b/lib/nodes/renderable_couple.js new file mode 100644 index 0000000..39ab886 --- /dev/null +++ b/lib/nodes/renderable_couple.js @@ -0,0 +1,28 @@ +import { Node } from '@serpentity/serpentity'; + +import CoupledEntitiesComponent from '../components/coupled_entities'; +import PixiContainerComponent from '../components/pixi_container'; + +/** + * Node identifying an entity that has coupled entities and can be + * rendered + * + * @extends {external:Serpentity.Node} + * @class RenderableCoupleNode + */ +export default class RenderableCoupleNode extends Node { + +}; + +/** + * Holds the types that are used to identify a renderable couple + * + * @property {object} types + * @name types + * @memberof RenderableCoupleNode + */ +RenderableCoupleNode.types = { + coupledEntities: CoupledEntitiesComponent, + container: PixiContainerComponent +}; + diff --git a/lib/nodes/renderable_with_attributes.js b/lib/nodes/renderable_with_attributes.js new file mode 100644 index 0000000..817036c --- /dev/null +++ b/lib/nodes/renderable_with_attributes.js @@ -0,0 +1,30 @@ +import { Node } from '@serpentity/serpentity'; + +import AngleComponent from '../components/angle'; +import PixiContainerComponent from '../components/pixi_container'; +import PositionComponent from '@serpentity/components.position'; + +/** + * Node identifying a renderable entity, should have position and a + * symbol to render + * + * @extends {external:Serpentity.Node} + * @class RenderableWithAttributesNode + */ +export default class RenderableWithAttributesNode extends Node { + +}; + +/** + * Holds the types that are used to identify a renderable with external + * attributes + * + * @property {object} types + * @name types + * @memberof RenderableWithAttributesNode + */ +RenderableWithAttributesNode.types = { + angle: AngleComponent, + position: PositionComponent, + container: PixiContainerComponent +}; diff --git a/lib/sumo.js b/lib/sumo.js index 81f948f..d79826f 100644 --- a/lib/sumo.js +++ b/lib/sumo.js @@ -1,7 +1,12 @@ +import PhysicsWorldControlSystem from './systems/physics_world_control'; +import PhysicsToAttributesSystem from './systems/physics_to_attributes'; +import CreateCouplingLineSystem from './systems/create_coupling_line'; import RenderSystem from './systems/render'; +import AttributesToRenderableSystem from './systems/attributes_to_renderable'; import SumoFactory from './factories/sumo'; import Serpentity from '@serpentity/serpentity'; import { Application } from 'pixi.js'; +import { Engine } from 'matter-js'; /* global window document */ @@ -60,6 +65,7 @@ internals.Sumo = class Sumo { // Initialization functions this._initializeCanvas(); + this._initializeMatter(); this._initializePixi(); this._initializeSystems(); this._initializeEntities(); @@ -124,6 +130,15 @@ internals.Sumo = class Sumo { window.addEventListener('resize', this._resizeCanvas.bind(this)); } + // Initialize MatterJs + + _initializeMatter() { + + this._matterJs = Engine.create(); + + this._matterJs.world.gravity.y = 0; + } + // Initialize Pixi _initializePixi() { @@ -160,6 +175,16 @@ internals.Sumo = class Sumo { _initializeSystems() { + this._engine.addSystem(new PhysicsWorldControlSystem({ + engine: this._matterJs + })); + + this._engine.addSystem(new PhysicsToAttributesSystem()); + + this._engine.addSystem(new AttributesToRenderableSystem()); + + this._engine.addSystem(new CreateCouplingLineSystem()); + this._engine.addSystem(new RenderSystem({ application: this._pixi })); @@ -169,19 +194,42 @@ internals.Sumo = class Sumo { _initializeEntities() { - SumoFactory.createSumo(this._engine, { + const entityA = SumoFactory.createSumo(null, { position: { x: 50, y: 50 } }); - SumoFactory.createSumo(this._engine, { + const entityB = SumoFactory.createSumo(null, { position: { x: 309, y: 112 } }); + + const entityC = SumoFactory.createSumo(null, { + position: { + x: 500, + y: 78 + } + }); + + SumoFactory.createRubberBand(this._engine, { + entityA, + entityB + }); + + SumoFactory.createRubberBand(this._engine, { + entityA: entityC, + entityB + }); + + // To keep the coupling behind, we'll manually add the sumos later + + this._engine.addEntity(entityA); + this._engine.addEntity(entityB); + this._engine.addEntity(entityC); } }; diff --git a/lib/systems/attributes_to_renderable.js b/lib/systems/attributes_to_renderable.js new file mode 100644 index 0000000..e5c66a0 --- /dev/null +++ b/lib/systems/attributes_to_renderable.js @@ -0,0 +1,73 @@ +import { System } from '@serpentity/serpentity'; + +import RenderableWithAttributesNode from '../nodes/renderable_with_attributes'; + +/** + * Updates the renderables based on their attribuets + * + * @extends {external:Serpentity.System} + * @class AttributesToRenderableSystem + * @param {object} config a configuration object to extend. + */ +export default class AttributesToRenderableSystem extends System { + + constructor(config = {}) { + + super(); + + /** + * The node collection of renderable entities + * + * @property {external:Serpentity.NodeCollection} renderables + * @instance + * @memberof AttributesToRenderableSystem + */ + this.renderables = null; + } + + /** + * Initializes system when added. Requests renderable nodes and + * attaches to event listeners to add / remove them to pixi stage + * + * @function added + * @memberof AttributesToRenderableSystem + * @instance + * @param {external:Serpentity.Engine} engine the serpentity engine to + * which we are getting added + */ + added(engine) { + + this.renderables = engine.getNodes(RenderableWithAttributesNode); + } + + /** + * Clears system resources when removed. + * + * @function removed + * @instance + * @memberof AttributesToRenderableSystem + */ + removed() { + + this.renderables = null; + } + + /** + * Runs on every update of the loop. Updates the graphics so they're + * rendered correctly + * + * @function update + * @instance + * @param {Number} currentFrameDuration the duration of the current + * frame + * @memberof AttributesToRenderableSystem + */ + update(currentFrameDuration) { + + for (const renderable of this.renderables) { + renderable.container.container.position.x = renderable.position.x; + renderable.container.container.position.y = renderable.position.y; + renderable.container.container.rotation = renderable.angle.angle; + } + } +}; diff --git a/lib/systems/create_coupling_line.js b/lib/systems/create_coupling_line.js new file mode 100644 index 0000000..8e5e9d7 --- /dev/null +++ b/lib/systems/create_coupling_line.js @@ -0,0 +1,96 @@ +import { System } from '@serpentity/serpentity'; +import { Graphics } from 'pixi.js'; + +import PositionComponent from '@serpentity/components.position'; + +import RenderableCoupleNode from '../nodes/renderable_couple'; + +/** + * Renders renderable objects using pixi + * + * @extends {external:Serpentity.System} + * @class CreateCouplingLineSystem + * @param {object} config a configuration object to extend. + */ +export default class CreateCouplingLineSystem extends System { + + constructor(config = {}) { + + super(); + + /** + * The node collection of coupled entities entities + * + * @property {external:Serpentity.NodeCollection} coupledEntities + * @instance + * @memberof CreateCouplingLineSystem + */ + this.coupledEntities = null; + } + + /** + * Initializes system when added. Requests renderable nodes and + * + * @function added + * @memberof CreateCouplingLineSystem + * @instance + * @param {external:Serpentity.Engine} engine the serpentity engine to + * which we are getting added + */ + added(engine) { + + this.coupledEntities = engine.getNodes(RenderableCoupleNode); + } + + /** + * Clears system resources when removed. + * + * @function removed + * @instance + * @memberof CreateCouplingLineSystem + */ + removed() { + + this.coupledEntities = null; + } + + /** + * Runs on every update of the loop. Does nothing. + * + * @function update + * @instance + * @param {Number} currentFrameDuration the duration of the current + * frame + * @memberof CreateCouplingLineSystem + */ + update(currentFrameDuration) { + + for (const rootEntity of this.coupledEntities) { + + const rootGraphic = rootEntity.container.container; + rootGraphic.removeChildren(); + + let lineGraphic = new Graphics(); + lineGraphic = lineGraphic.lineStyle(5, 0xffffff, 0.9); + + let initialPosition = false; + + for (const coupledEntity of rootEntity.coupledEntities.coupledEntities) { + if (coupledEntity.hasComponent(PositionComponent)) { + + const position = coupledEntity.getComponent(PositionComponent); + + if (!initialPosition) { + lineGraphic = lineGraphic.moveTo(position.x, position.y); + initialPosition = true; + continue; + } + lineGraphic = lineGraphic.lineTo(position.x, position.y); + } + } + + rootGraphic.addChild(lineGraphic); + } + } +}; + diff --git a/lib/systems/physics_to_attributes.js b/lib/systems/physics_to_attributes.js new file mode 100644 index 0000000..a49a16b --- /dev/null +++ b/lib/systems/physics_to_attributes.js @@ -0,0 +1,72 @@ +import { System } from '@serpentity/serpentity'; + +import PhysicalWithAttributesNode from '../nodes/physical_with_attributes'; + +/** + * Distribuets physics data to the related components + * + * @extends {external:Serpentity.System} + * @class PhysicsToAttributesSystem + * @param {object} config a configuration object to extend. + */ +export default class PhysicsToAttributesSystem extends System { + + constructor(config = {}) { + + super(); + + /** + * The node collection of physics entities + * + * @property {external:Serpentity.NodeCollection} physicalEntities + * @instance + * @memberof PhysicsToAttributesSystem + */ + this.physicalEntities = null; + } + + /** + * Initializes system when added. Requests physics nodes + * + * @function added + * @memberof PhysicsToAttributesSystem + * @instance + * @param {external:Serpentity.Engine} engine the serpentity engine to + * which we are getting added + */ + added(engine) { + + this.physicalEntities = engine.getNodes(PhysicalWithAttributesNode); + } + + /** + * Clears system resources when removed. + * + * @function removed + * @instance + * @memberof PhysicsToAttributesSystem + */ + removed() { + + this.physicalEntities = null; + } + + /** + * Runs on every update of the loop. Updates the other components + * based on physics + * + * @function update + * @instance + * @param {Number} currentFrameDuration the duration of the current + * frame + * @memberof PhysicsToAttributesSystem + */ + update(currentFrameDuration) { + + for (const physicalEntity of this.physicalEntities) { + physicalEntity.position.x = physicalEntity.body.body.position.x; + physicalEntity.position.y = physicalEntity.body.body.position.y; + physicalEntity.angle.angle = physicalEntity.body.body.angle; + } + } +}; diff --git a/lib/systems/physics_world_control.js b/lib/systems/physics_world_control.js new file mode 100644 index 0000000..daa39c1 --- /dev/null +++ b/lib/systems/physics_world_control.js @@ -0,0 +1,98 @@ +import { System } from '@serpentity/serpentity'; +import { Engine, World } from 'matter-js'; + +import PhysicalNode from '../nodes/physical'; + +const internals = { + kNoEngine: 'No matter js physics engine found. Make sure you set the `engine` key in the config object when initializing.' +}; + +/** + * Adds and removes objects to the physics world and calls update + * + * @extends {external:Serpentity.System} + * @class PhysicsWorldControlSystem + * @param {object} config a configuration object to extend. + */ +export default class PhysicsWorldControlSystem extends System { + + constructor(config = {}) { + + super(); + + /** + * The node collection of physics entities + * + * @property {external:Serpentity.NodeCollection} physicalEntities + * @instance + * @memberof PhysicsWorldControlSystem + */ + this.physicalEntities = null; + + /** + * The matter-js engine we will use to process physics + * + * @property {external:MatterJs.Engine} engine + * @instance + * @memberof PhysicsWorldControlSystem + */ + this.engine = config.engine; + + if (!this.engine) { + throw new Error(internals.kNoEngine); + } + } + + /** + * Initializes system when added. Requests physics nodes and + * attaches to event listeners to add / remove them to pixi stage + * + * @function added + * @memberof PhysicsWorldControlSystem + * @instance + * @param {external:Serpentity.Engine} engine the serpentity engine to + * which we are getting added + */ + added(engine) { + + this.physicalEntities = engine.getNodes(PhysicalNode); + this.physicalEntities.on('nodeAdded', (event) => { + + World.add(this.engine.world, [event.node.body.body]); + }); + this.physicalEntities.on('nodeRemoved', (event) => { + + World.remove(this.engine.world, [event.node.body.body]); + }); + } + + /** + * Clears system resources when removed. + * + * @function removed + * @instance + * @memberof PhysicsWorldControlSystem + */ + removed() { + + this.physicalEntities.removeAllListeners('nodeAdded'); + this.physicalEntities.removeAllListeners('nodeRemoved'); + this.physicalEntities = null; + } + + /** + * Runs on every update of the loop. Updates the physics + * + * @function update + * @instance + * @param {Number} currentFrameDuration the duration of the current + * frame + * @memberof PhysicsWorldControlSystem + */ + update(currentFrameDuration) { + + Engine.run(this.engine); + } +}; + + diff --git a/lib/systems/render.js b/lib/systems/render.js index 8d3d446..a2e3020 100644 --- a/lib/systems/render.js +++ b/lib/systems/render.js @@ -7,7 +7,7 @@ const internals = { }; /** - * Renders renderable objects to console + * Renders renderable objects using pixi * * @extends {external:Serpentity.System} * @class RenderSystem @@ -80,8 +80,7 @@ export default class RenderSystem extends System { } /** - * Runs on every update of the loop. Prints the location of every - * renderable + * Runs on every update of the loop. Does nothing. * * @function update * @instance @@ -89,11 +88,5 @@ export default class RenderSystem extends System { * frame * @memberof RenderSystem */ - update(currentFrameDuration) { - - for (const renderable of this.renderables) { - renderable.container.container.position.x = renderable.position.x; - renderable.container.container.position.y = renderable.position.y; - } - } + update(currentFrameDuration) {} }; diff --git a/package-lock.json b/package-lock.json index 43c41d3..17d34da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5445,6 +5445,11 @@ "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, + "matter-js": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/matter-js/-/matter-js-0.14.1.tgz", + "integrity": "sha512-qbPBf5NpjPfNlxiRXpoJHAnMMF4WNP22mRhkas0hsUnVTztFBt3QpxRChEws4j1YuaH2bCXEd2i4qmRwADqyuA==" + }, "md5.js": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", diff --git a/package.json b/package.json index 50f2a47..21ab9f3 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@serpentity/components.debug": "^2.0.0", "@serpentity/components.position": "^2.0.0", "@serpentity/serpentity": "^2.1.0", + "matter-js": "^0.14.1", "pixi.js": "^4.7.3" }, "browserslist": "last two versions",