Skip to content

Commit

Permalink
✨ ct.matter module for 2D physics. See the new example!
Browse files Browse the repository at this point in the history
  • Loading branch information
CosmoMyzrailGorynych committed Mar 28, 2021
1 parent 0f47d90 commit 6e0fd82
Show file tree
Hide file tree
Showing 74 changed files with 5,646 additions and 0 deletions.
15 changes: 15 additions & 0 deletions app/data/ct.libs/matter/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = {
"env": {
"browser": true,
"es6": true
},
"extends": "eslint:recommended",
"globals": {
"Matter": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
}
};
5 changes: 5 additions & 0 deletions app/data/ct.libs/matter/docs/Additional utility functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Additional functions

## `ct.matter.getImpact(pair)`

Calculates a good approximation of an impact two colliding copies caused to each other. The impact is calculated based on copies' velocity relative to each other and their mass. It does not account for impulse transferred through several copies.
12 changes: 12 additions & 0 deletions app/data/ct.libs/matter/docs/Advanced usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Advanced usage

This module bundles the whole Matter.js library. Though it is possible to make an entire game not using Matter.js methods, there are tons of advanced APIs that allow creating soft bodies, 2D cars, ragdolls, complex compound shapes, collision filtering, and other things.

First of all, you can get the physical bodies that are used by copies with `this.matterBody`. Each of these bodies have a backwards reference to ct.js copies at `body.copy`, where `body` is a Matter.js physical body.

All the Matter.js methods are available under the `Matter` object. For example, you can call `Matter.Body.setMass(this.matterBody, 100)` to dynamically change the mass of a copy.

## Further reading

* [Matter.js docs](https://brm.io/matter-js/docs/index.html)
* [Demos](https://brm.io/matter-js/demo/#wreckingBall). You can see their source code by clicking the `{}` icon at the top of each demo.
19 changes: 19 additions & 0 deletions app/data/ct.libs/matter/docs/Creating constraints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Creating physical constraints

This module has a couple of methods that simplify the creation of constraints between two copies, or a copy and a position in space. These constraints create a spring, or a rope, that limits a copy's movement.

## `ct.matter.pin(copy)`

Pins a copy in place, making it spin around its center of mass but preventing any other movement.

## `ct.matter.tie(copy, position, stiffness = 0.05, damping = 0.05)`

Ties a copy to a specific position in space, making it swing around it.

## `ct.matter.rope(copy, length, stiffness = 0.05, damping = 0.05)`

Puts a copy on a rope. It is similar to `ct.matter.tie`, but the length of a rope is defined explicitly, and starts from the copy's current position.

## `ct.matter.tieTogether(copy1, copy2, stiffness, damping)`

Ties two copies together with a rope.
38 changes: 38 additions & 0 deletions app/data/ct.libs/matter/docs/Listening to collision events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Listening to collision events

You can listen to collision events with `ct.matter.on(eventName, callback)`. The callback is passed an object that has `pairs` property, which has all the collisions that happened in one frame. Long story short, see this example:

```js
// Room's OnCreate code
// Listen for collisions in the world
ct.matter.on('collisionStart', e => {
// Loop over every collision in a frame
for (var pair of e.pairs) {
// Get how strong the impact was
// We will use it for damage calculation to aliens
var impact = ct.matter.getImpact(pair);

// Each pair has bodyA and bodyB — two objects that has collided.
// This little loop applies checks for both bodies
var bodies = [pair.bodyA, pair.bodyB];
for (var body of bodies) {
// Here, body.copy is the ct.js copy that owns this physical body.
// Does a body belong to a copy of type "Alien"?
if (body.copy.type === 'Alien') {
// If the impact was too strong, destroy the alien.
if (impact > 75) {
body.copy.kill = true;
}
}
}
}
});
```

> **Warning**: DO NOT write `ct.matter.on` inside copies' code. This will apply large amounts of listeners that do the same thing, which will degrade performance and break your gameplay logic. Instead, write `ct.matter.on` once in room's On Create code.
There are three collision events you can listen to:

* `collisionStart` — objects has struck each other, or an object entered a sensor's area.
* `collisionActive` — called on each frame for all the current collisions.
* `collisionEnd` — objects stopped colliding, or an object has left sensor's area.
73 changes: 73 additions & 0 deletions app/data/ct.libs/matter/docs/Manipulating copies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Manipulating copies.

`ct.matter` requires you to use specific methods to move and transform your copies. Usual parameter-based manipulations won't work.

## `ct.matter.teleport(copy, x, y);`

Moves a copy to a new position without changing its velocity.

| Argument | Type | Description |
| -------- | -------- | --------------------------- |
| `copy` | `Copy` | The ct.js copy to teleport. |
| `x` | `number` | The new X coordinate. |
| `y` | `number` | The new Y coordinate. |

## `ct.matter.push(copy, forceX, forceY, fromX, fromY);`

Applies a force onto a copy. The resulting velocity depends on object's mass and friction.
You can optionally define a point from which the force is applied to make the copy spin.

| Argument | Type | Description |
| ------------------ | -------- | ------------------------------------------------------- |
| `copy` | `Copy` | The copy that should be pushed. |
| `forceX` | `number` | The force applied along the horizontal axis. |
| `forceY` | `number` | The force applied along the vertical axis. |
| `fromX` (optional) | `number` | An optional X coordinate from which to apply the force. |
| `fromY` (optional) | `number` | An optional Y coordinate from which to apply the force. |

## `ct.matter.spin(copy, speed);`

Sets copy's angular velocity.

| Argument | Type | Description |
| -------- | -------- | -------------------------------- |
| `copy` | `Copy` | The copy that should be spinned. |
| `speed` | `number` | New angular velocity |

## `ct.matter.rotate(copy, angle);`

Rotates copy to the defined angle.

| Argument | Type | Description |
| -------- | -------- | -------------------------------- |
| `copy` | `Copy` | The copy that should be rotated. |
| `angle` | `number` | New angle. |

## `ct.matter.rotateBy(copy, angle);`

Rotates copy by the defined angle.

| Argument | Type | Description |
| -------- | -------- | -------------------------------- |
| `copy` | `Copy` | The copy that should be rotated. |
| `angle` | `number` | How much to turn the copy. |

## `ct.matter.scale(copy, x, y);`

Scales the copy and its physical object.

| Argument | Type | Description |
| -------- | -------- | ------------------------------------ |
| `copy` | `Copy` | The copy that should be scaled |
| `x` | `number` | New scale value, by horizontal axis. |
| `y` | `number` | New scale value, by vertical axis. |

## `ct.matter.launch(copy, hspeed, vspeed);`

Sets the copy's velocity, instantly and magically.

| Argument | Type | Description |
| -------- | -------- | --------------------------------------- |
| `copy` | `Copy` | The copy which speed should be changed. |
| `hspeed` | `number` | New horizontal speed. |
| `vspeed` | `number` | New vertical speed. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Read this or you will create a black hole

Working with copies when `ct.matter` is enabled is drastically different from the regular workflow.

## `vspeed`, `hspeed`, `scale`, `x`, `y` and many others are read-only

Changing any of these values will have no effect as Matter.js provides properties a bit more complex than these. Instead of that, use these methods:

* `ct.matter.teleport(copy, x, y)` to move a copy in an arbitrary place;
* `ct.matter.push(copy, forceX, forceY)` to apply a force so that a copy gradually accelerates where you want;
* `ct.matter.launch(copy, hspeed, vspeed)` to set copy's velocity. Use it for actions like jumpts, or for explosion impacts.
* `ct.matter.spin(copy, speed)` to set angular velocity.
* `ct.matter.rotate(copy, amgle)` to set copy's orientation.
* `ct.matter.scale(copy, x, y)` to resize a copy.

You can still read `x`, `y`, `hspeed`, `vspeed`, `speed`, `direction`, but these are to be considered read-only.

See "Manipulating copies" page for more information.

## Remove `this.move();`

`this.move();` will conflict with the physics system and cause jittering and other unpleasanties.

## Texture's axis is the center of mass

This affects how a copy rotates when in contact with other copies. The axis must be inside the collision shape.

## Collision logic is defined differently

Instead of testing for collisions from behalf of a particular copy like it happens in `ct.place`, you will define a rulebook that will listen to all the collisions in a room, and you will filter out these collisions according to your gameplay logic. Due to that, **never** listen to these events in copies, as each your copy will have to loop over all the collision events, hindering performance badly. Instead, set up a listener once in your room's OnCreate code.

See "Listening to collision events" for more information.
6 changes: 6 additions & 0 deletions app/data/ct.libs/matter/includes/matter.min.js

Large diffs are not rendered by default.

174 changes: 174 additions & 0 deletions app/data/ct.libs/matter/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
ct.matter = {
on(event, callback) {
Matter.Events.on(ct.room.matterEngine, event, callback);
},
off(event, callback) {
Matter.Events.off(ct.room.matterEngine, event, callback);
},

teleport(copy, x, y) {
Matter.Body.setPosition(copy.matterBody, x, y);
copy.x = x;
copy.y = y;
},
push(copy, forceX, forceY, fromX, fromY) {
if (fromX === void 0) {
Matter.Body.applyForce(copy.matterBody, copy.position, {
x: forceX,
y: forceY
});
} else {
Matter.Body.applyForce(copy.matterBody, {
x: fromX,
y: fromY
}, {
x: forceX,
y: forceY
});
}
},
spin(copy, speed) {
Matter.Body.setAngularVelocity(copy.matterBody, -ct.u.radToDeg(speed));
},
rotate(copy, angle) {
Matter.Body.setAmgle(copy.matterBody, -ct.u.radToDeg(angle));
},
rotateBy(copy, angle) {
Matter.Body.rotate(copy.matterBody, -ct.u.radToDeg(angle));
},
scale(copy, x, y) {
Matter.Body.scale(copy, x, y);
copy.scale.x = x;
copy.scale.y = y;
},
launch(copy, hspeed, vspeed) {
Matter.Body.setVelocity(copy.matterBody, {
x: hspeed,
y: vspeed
});
},

pin(copy) {
const constraint = Matter.Constraint.create({
bodyB: copy.matterBody,
pointA: {
x: copy.x,
y: copy.y
},
length: 0
});
Matter.World.add(ct.room.matterWorld, constraint);
return constraint;
},
tie(copy, position, stiffness = 0.05, damping = 0.05) {
const constraint = Matter.Constraint.create({
bodyB: copy.matterBody,
pointA: position,
pointB: {
x: 0,
y: 0
},
stiffness,
damping
});
Matter.World.add(ct.room.matterWorld, constraint);
return constraint;
},
rope(copy, length, stiffness = 0.05, damping = 0.05) {
const constraint = Matter.Constraint.create({
pointA: copy.position,
bodyB: copy.matterBody,
length,
stiffness,
damping
});
Matter.World.add(ct.room.matterWorld, constraint);
return constraint;
},
tieTogether(copy1, copy2, stiffness, damping) {
const constraint = Matter.Constraint.create({
bodyA: copy1.matterBody,
bodyB: copy2.matterBody,
stiffness,
damping
});
Matter.World.add(ct.room.matterWorld, constraint);
return constraint;
},
onCreate(copy) {
const options = {
isStatic: copy.matterStatic,
isSensor: copy.matterSensor,
restitution: copy.matterRestitution || 0.1,
friction: copy.matterFriction === void 0 ? 1 : copy.matterFriction,
frictionStatic: copy.matterFrictionStatic === void 0 ? 0.1 : copy.matterFrictionStatic,
frictionAir: copy.matterFrictionAir || 0.01,
density: copy.matterDensity || 0.001
};
if (copy.shape.type === 'rect') {
copy.matterBody = Matter.Bodies.rectangle(
copy.x - copy.shape.left,
copy.y - copy.shape.top,
copy.shape.left + copy.shape.right,
copy.shape.top + copy.shape.bottom,
options
);
}
if (copy.shape.type === 'circle') {
copy.matterBody = Matter.Bodies.circle(
copy.x,
copy.y,
copy.shape.r,
options
);
}
if (copy.shape.type === 'strip') {
const vertices = Matter.Vertices.create(copy.shape.points);
copy.matterBody = Matter.Bodies.fromVertices(copy.x, copy.y, vertices, options);
}

Matter.Body.setCentre(copy.matterBody, {
x: (copy.texture.defaultAnchor.x - 0.5) * copy.texture.width,
y: (copy.texture.defaultAnchor.y - 0.5) * copy.texture.height
}, true);
Matter.Body.setPosition(copy.matterBody, copy.position);
Matter.Body.setAngle(copy.matterBody, ct.u.degToRad(copy.angle));
Matter.Body.scale(copy.matterBody, copy.scale.x, copy.scale.y);

Matter.World.add(ct.room.matterWorld, copy.matterBody);
copy.matterBody.copy = copy;

if (copy.matterFixPivot && copy.matterFixPivot[0]) {
[copy.pivot.x] = copy.matterFixPivot;
}
if (copy.matterFixPivot && copy.matterFixPivot[1]) {
[, copy.pivot.y] = copy.matterFixPivot;
}

if (copy.matterConstraint === 'pinpoint') {
copy.constraint = ct.matter.pin(copy);
} else if (copy.matterConstraint === 'rope') {
copy.constraint = ct.matter.rope(
copy,
copy.matterRopeLength === 0 ? 64 : copy.matterRopeLength,
copy.matterRopeStiffness === 0 ? 0.05 : copy.matterRopeStiffness,
copy.matterRopeDamping === 0 ? 0.05 : copy.matterRopeDamping
);
}
},
getImpact(pair) {
const {bodyA, bodyB} = pair;
// Because static objects are Infinity-ly heavy, and Infinity * 0 returns NaN,
// We should compute mass for static objects manually.
const massA = bodyA.mass === Infinity ? 0 : bodyA.mass,
massB = bodyB.mass === Infinity ? 0 : bodyB.mass;
const impact = /*(bodyA.mass + bodyB.mass) */ ct.u.pdc(
// This tells how much objects are moving in opposite directions
bodyA.velocity.x * massA,
bodyA.velocity.y * massA,
bodyB.velocity.x * massB,
bodyB.velocity.y * massB
);
return impact;
}
};
9 changes: 9 additions & 0 deletions app/data/ct.libs/matter/injects/afterdraw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
if (this.matterEnable) {
this.angle = -ct.u.radToDeg(this.matterBody.angle);
this.x = this.matterBody.position.x;
this.y = this.matterBody.position.y;
this.speed = this.matterBody.speed;
this.hspeed = this.matterBody.velocity.x;
this.vspeed = this.matterBody.velocity.y;
this.direction = ct.u.pdn(this.hspeed, this.vspeed);
}
5 changes: 5 additions & 0 deletions app/data/ct.libs/matter/injects/beforeroomdraw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
if ([/*%matterUseStaticDeltaTime%*/][0] === false) {
Matter.Engine.update(ct.room.matterEngine, 1000 / ct.speed * ct.delta);
} else {
Matter.Engine.update(ct.room.matterEngine, 1000 / ct.speed);
}
4 changes: 4 additions & 0 deletions app/data/ct.libs/matter/injects/beforeroomoncreate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ct.room.matterEngine = Matter.Engine.create();
ct.room.matterWorld = ct.room.matterEngine.world;
ct.room.matterGravity = ct.room.matterGravity || [0, 9.8];
[ct.room.matterWorld.gravity.x, ct.room.matterWorld.gravity.y] = ct.room.matterGravity;
1 change: 1 addition & 0 deletions app/data/ct.libs/matter/injects/htmlbottom.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script type="text/javascript" src="matter.min.js"></script>
Loading

0 comments on commit 6e0fd82

Please sign in to comment.