-
Notifications
You must be signed in to change notification settings - Fork 0
/
calculate-physics.ts
129 lines (106 loc) · 4.13 KB
/
calculate-physics.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import {
BodyComponent,
CollidedEvent,
GameStateResource,
RESOURCE_NAMES,
SettingsResource,
TimeResource
} from "@worldscapes-arkanoid/common";
import {
AddComponentCommand,
ComponentPurposes,
ComponentSelector,
DeleteComponentCommand,
ECRCommand,
ECRRule,
EntityRequest,
isSet,
ResourcePurposes,
ResourceRequest,
UpdateComponentCommand
} from "@worldscapes/common";
import * as Matter from "matter-js";
import {Engine, IEventCollision} from "matter-js";
const engine = Matter.Engine.create();
engine.gravity.y = 0;
engine.constraintIterations = 6;
engine.positionIterations = 30;
engine.velocityIterations = 30;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Matter.Resolver._restingThresh = 0.5;
export const calculatePhysics = ECRRule.create({
query: {
entity: {
bodies: new EntityRequest({
body: new ComponentSelector(ComponentPurposes.WRITE, BodyComponent),
}),
events: new EntityRequest({
body: new ComponentSelector(ComponentPurposes.WRITE, BodyComponent),
event: new ComponentSelector(ComponentPurposes.WRITE, CollidedEvent)
}),
},
resource: {
gameState: new ResourceRequest<GameStateResource, typeof ResourcePurposes.READ>(ResourcePurposes.READ, RESOURCE_NAMES.gameState),
settings: new ResourceRequest<SettingsResource, typeof ResourcePurposes.READ>(ResourcePurposes.READ, RESOURCE_NAMES.settings),
time: new ResourceRequest<TimeResource, typeof ResourcePurposes.READ>(ResourcePurposes.READ, RESOURCE_NAMES.time),
},
},
condition: ({ resource: { gameState, settings, time } }) => isSet(gameState) && gameState.isStarted && isSet(settings) && isSet(time),
body: ({ entity: { bodies, events }, resource: { settings, time } }) => {
const commands: ECRCommand[] = [];
// Clear previous events
events.forEach(event => {
commands.push(new DeleteComponentCommand(event.entityId, event.event));
});
// Copy Bodies to keep them not mutated
const copiedBodies = new Map<number, Matter.Body>();
bodies.forEach(bodyEntity => {
const copy = Matter.Body.create({
...bodyEntity.body.instance,
parent: undefined,
parts: [],
});
copy['__entityId'] = bodyEntity.entityId;
copiedBodies.set(bodyEntity.entityId, copy);
});
// Run simulation tick
Matter.Composite.add(engine.world, Array.from(copiedBodies.values()));
// Listen collision events
function handleCollisions(events: IEventCollision<Engine>) {
const collidedBodies: Matter.Body[] = [];
events.pairs.forEach(pair => {
collidedBodies.push(pair.bodyA);
collidedBodies.push(pair.bodyB);
});
const uniqueCollidedBodies = Array.from(new Set(collidedBodies));
uniqueCollidedBodies.forEach(body => {
commands.push(
new AddComponentCommand(
body['__entityId'],
new CollidedEvent(Date.now())
)
);
});
}
Matter.Events.on(engine, 'collisionEnd', handleCollisions);
// Run update
Matter.Engine.update(engine, time!.currentDelta * 1000, time!.currentDelta / time!.previousDelta);
// Clean collision listener
Matter.Events.off(engine, 'collisionEnd', handleCollisions);
// Clear engine
Matter.Composite.clear(engine.world, false);
// Update body components
const copiedTuples = Array.from(copiedBodies.entries());
bodies.forEach(bodyEntity => {
commands.push(
new UpdateComponentCommand(
bodyEntity.entityId,
bodyEntity.body,
new BodyComponent(copiedBodies.get(bodyEntity.entityId)!),
)
);
});
return commands;
},
});