forked from liabru/matter-attractors
-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
338 lines (296 loc) · 12.5 KB
/
index.js
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
"use strict";
const Matter = require('matter-js');
/**
* An attractors plugin for matter.js.
* See the readme for usage and examples.
* @module MatterAttractors
*/
const MatterAttractors = {
// plugin meta
name: 'matter-attractors', // PLUGIN_NAME
version: '0.1.6', // PLUGIN_VERSION
for: 'matter-js@^0.12.0',
// installs the plugin where `base` is `Matter`
// you should not need to call this directly.
install: function(base) {
base.after('Body.create', function() {
MatterAttractors.Body.init(this);
});
base.before('Engine.update', function(engine) {
MatterAttractors.Engine.update(engine);
});
},
Body: {
/**
* Initialises the `body` to support attractors.
* This is called automatically by the plugin.
* @function MatterAttractors.Body.init
* @param {Matter.Body} body The body to init.
* @returns {void} No return value.
*/
init: function(body) {
body.plugin.attractors = body.plugin.attractors || [];
}
},
Engine: {
/**
* Applies all attractors for all bodies in the `engine`.
* This is called automatically by the plugin.
* @function MatterAttractors.Engine.update
* @param {Matter.Engine} engine The engine to update.
* @returns {void} No return value.
*/
update: function(engine) {
let world = engine.world,
bodies = Matter.Composite.allBodies(world);
for (let i = 0; i < bodies.length; i += 1) {
let bodyA = bodies[i],
attractors = bodyA.plugin.attractors;
if (attractors && attractors.length > 0) {
for (let j = i + 1; j < bodies.length; j += 1) {
let bodyB = bodies[j];
for (let k = 0; k < attractors.length; k += 1) {
let attractor = attractors[k],
forceVector = attractor;
if (Matter.Common.isFunction(attractor)) {
forceVector = attractor(bodyA, bodyB);
}
if (forceVector) {
Matter.Body.applyForce(bodyB, bodyB.position, forceVector);
}
}
}
}
}
}
},
/**
* Defines some useful common attractor functions that can be used
* by pushing them to your body's `body.plugin.attractors` array.
* @namespace MatterAttractors.Attractors
* @property {number} gravityConstant The gravitational constant used by the gravity attractor.
*/
Attractors: {
gravityConstant: 0.001,
/**
* An attractor function that applies Newton's law of gravitation.
* Use this by pushing `MatterAttractors.Attractors.gravity` to your body's `body.plugin.attractors` array.
* The gravitational constant defaults to `0.001` which you can change
* at `MatterAttractors.Attractors.gravityConstant`.
* @function MatterAttractors.Attractors.gravity
* @param {Matter.Body} bodyA The first body.
* @param {Matter.Body} bodyB The second body.
* @returns {void} No return value.
*/
gravity: function(bodyA, bodyB) {
// use Newton's law of gravitation
var bToA = Matter.Vector.sub(bodyB.position, bodyA.position),
distanceSq = Matter.Vector.magnitudeSquared(bToA) || 0.0001,
normal = Matter.Vector.normalise(bToA),
magnitude = -MatterAttractors.Attractors.gravityConstant * (bodyA.mass * bodyB.mass / distanceSq),
force = Matter.Vector.mult(normal, magnitude);
// to apply forces to both bodies
Matter.Body.applyForce(bodyA, bodyA.position, Matter.Vector.neg(force));
Matter.Body.applyForce(bodyB, bodyB.position, force);
},
/**
* An attractor function that applies magnetic pull to `metallic` bodies.
* Use this by pushing `MatterAttractors.Attractors.magnet` to your body's `body.plugin.attractors` array.
* The magnetic pull defaults to `0` (implying it's metallic but not magnetic) which you can change to a
* positive number for a particular body by changing `body.plugin.magnet.pull`. Magnetic poles default to
* the midpoint between the body's first two vertices and the midpoint between the body's middle and
* middle-plus-one vertices. This can be changed by specifying two faces according to the body vertices'
* indexes at `body.plugin.magnet.north` and `body.plugin.magnet.south`. If the magnetic pull is `0`,
* magnetic poles are ignored for that body.
* @function MatterAttractors.Attractors.magnet
* @param {Matter.Body} bodyA The first body.
* @param {Matter.Body} bodyB The second body.
* @returns {void} No return value.
*/
magnet: (function () {
var northA = {cornerA: null, cornerB: null, x: 0, y: 0},
southA = {cornerA: null, cornerB: null, x: 0, y: 0},
northB = {cornerA: null, cornerB: null, x: 0, y: 0},
southB = {cornerA: null, cornerB: null, x: 0, y: 0},
emptyVector = {x: 0, y: 0},
getVector = function (face, point) { // This returns a directional unit vector for a point according to where it's located relative to the face of a pole.
var norm = Matter.Vector.perp(Matter.Vector.sub(face.cornerB, face.cornerA), true),
rel = Matter.Vector.sub(point, face),
targetNorm = null,
halfFaceSq = Matter.Vector.magnitudeSquared(Matter.Vector.sub(face.cornerA, face));
if (Matter.Vector.magnitudeSquared(rel) < halfFaceSq) {
return Matter.Vector.neg(Matter.Vector.add(rel, norm));
} else if (Matter.Vector.dot(norm, rel) <= 0) { // behind
return norm;
} else if (Matter.Vector.magnitudeSquared(Matter.Vector.sub(face.cornerA, point)) < halfFaceSq) { // near corner, need to pull towards face
rel = Matter.Vector.sub(point, face.cornerA);
return Matter.Vector.normalise(Matter.Vector.perp(rel, false));
} else if (Matter.Vector.magnitudeSquared(Matter.Vector.sub(face.cornerB, point)) < halfFaceSq) { // use corner B
rel = Matter.Vector.sub(point, face.cornerB);
return Matter.Vector.normalise(Matter.Vector.perp(rel, true));
} else { // farther away
targetNorm = Matter.Vector.mult(Matter.Vector.normalise(norm), -0.5 * Matter.Vector.magnitude(rel) / Math.abs(Math.cos(Matter.Vector.angle(rel, norm))));
return Matter.Vector.sub(targetNorm, rel);
}
},
getForce = function (poleA, poleB, pull, distanceSq) {
var direction = getVector(poleA, poleB),
normal = Matter.Vector.normalise(direction),
magnitude = -pull / Math.max(pull * 10, distanceSq);
return Matter.Vector.mult(normal, magnitude);
},
applyForce = function (poleA, poleB, bodyA, bodyB, pullA, pullB) {
var bToA = Matter.Vector.sub(poleB, poleA),
distanceSq = Matter.Vector.magnitudeSquared(bToA),
forceA = pullA ? Matter.Vector.neg(getForce(poleA, poleB, pullA, distanceSq)) : emptyVector,
forceB = pullB ? getForce(poleB, poleA, pullB, distanceSq) : emptyVector,
force = Matter.Vector.add(forceA, forceB);
// to apply forces to both bodies
Matter.Body.applyForce(bodyA, poleA, Matter.Vector.neg(force));
Matter.Body.applyForce(bodyB, poleB, force);
},
getPole = function (index, vertices, pole) {
var length = vertices.length,
a = vertices[index % length],
b = vertices[(index + 1) % length];
pole.cornerA = a;
pole.cornerB = b;
pole.x = (a.x + b.x) / 2;
pole.y = (a.y + b.y) / 2;
return pole;
},
getSouthPole = function (magnet, vertices, pole) {
if (typeof magnet.south !== 'number') {
magnet.south = vertices.length / 2 >> 0;
}
return getPole(magnet.south, vertices, pole);
},
getNorthPole = function (magnet, vertices, pole) {
if (typeof magnet.north !== 'number') {
magnet.north = 0;
}
return getPole(magnet.north, vertices, pole);
};
return function (bodyA, bodyB) {
var magnetA = bodyA.plugin.magnet,
magnetB = bodyB.plugin.magnet,
poleANorth = null,
poleASouth = null,
poleBNorth = null,
poleBSouth = null,
pullA = 0,
pullB = 0,
repel = -1,
verticesA = bodyA.vertices,
verticesB = bodyB.vertices;
if (!magnetA) {
magnetA = bodyA.plugin.magnet = {};
}
if (!magnetB || !(magnetA.pull || magnetB.pull)) {
return;
}
pullA = magnetA.pull || 0;
pullB = magnetB.pull || 0;
if (pullA) {
poleANorth = getNorthPole(magnetA, verticesA, northA);
poleASouth = getSouthPole(magnetA, verticesA, southA);
} else {
poleANorth = poleASouth = bodyA.position;
repel = 1;
}
if (pullB) {
poleBNorth = getNorthPole(magnetB, verticesB, northB);
poleBSouth = getSouthPole(magnetB, verticesB, southB);
} else {
poleBNorth = poleBSouth = bodyB.position;
repel = 1;
}
applyForce(poleANorth, poleBNorth, bodyA, bodyB, repel * pullA, repel * pullB);
applyForce(poleANorth, poleBSouth, bodyA, bodyB, pullA, pullB);
applyForce(poleASouth, poleBNorth, bodyA, bodyB, pullA, pullB);
applyForce(poleASouth, poleBSouth, bodyA, bodyB, repel * pullA, repel * pullB);
};
}()),
fan: (function () {
var project = function (fromV, toV) {
return Matter.Vector.mult(toV, Matter.Vector.dot(fromV, toV) / Matter.Vector.dot(toV, toV));
},
applyForce = function (push, cornerA, cornerB, item, fan, moveFan) {
var face = Matter.Vector.sub(cornerB, cornerA),
norm = Matter.Vector.perp(face, true),
rel = Matter.Vector.sub(item.position, cornerA),
projection = null,
pSq = 0,
distanceSq = 0,
pushiness = 0,
force = null;
if (Matter.Vector.dot(norm, rel) <= 0) { // behind
return;
}
projection = project(rel, face);
pSq = Matter.Vector.magnitudeSquared(projection);
if (pSq > Matter.Vector.magnitudeSquared(face)) {
rel = Matter.Vector.sub(rel, face);
item.torque += push / Math.max(push * 10, Matter.Vector.magnitudeSquared(Matter.Vector.sub(projection, face)));
} else if (Matter.Vector.dot(projection, face) > 0) {
rel = Matter.Vector.sub(rel, projection);
} else {
item.torque -= push / Math.max(push * 10, pSq);
}
distanceSq = Matter.Vector.magnitudeSquared(rel);
pushiness = Matter.Vector.magnitudeSquared(project(rel, norm)) / distanceSq;
pushiness *= push / Math.max(push * 10, distanceSq);
force = Matter.Vector.mult(Matter.Vector.normalise(rel), pushiness);
// to apply forces to both bodies
Matter.Body.applyForce(item, item.position, force);
if (moveFan) {
Matter.Body.applyForce(fan, fan.position, Matter.Vector.neg(force));
}
},
getCorner = function (index, vertices) {
return vertices[index % vertices.length];
};
return function (bodyA, bodyB) {
var faces = null,
fan = bodyA.plugin.fan,
i = 0,
vertices = bodyA.vertices;
if (!fan || !fan.push) {
return;
}
if (!fan.faces) {
faces = fan.faces = [0];
} else {
faces = fan.faces;
}
i = faces.length;
while (i--) {
applyForce(fan.push, getCorner(faces[i], vertices), getCorner(faces[i] + 1, vertices), bodyB, bodyA, fan.move);
}
};
}())
}
};
Matter.Plugin.register(MatterAttractors);
module.exports = MatterAttractors;
/**
* @namespace Matter.Body
* @see http://brm.io/matter-js/docs/classes/Body.html
*/
/**
* This plugin adds a new property `body.plugin.attractors` to instances of `Matter.Body`.
* This is an array of callback functions that will be called automatically
* for every pair of bodies, on every engine update.
* @property {Function[]} body.plugin.attractors
* @memberof Matter.Body
*/
/**
* An attractor function calculates the force to be applied
* to `bodyB`, it should either:
* - return the force vector to be applied to `bodyB`
* - or apply the force to the body(s) itself
* @callback AttractorFunction
* @param {Matter.Body} bodyA
* @param {Matter.Body} bodyB
* @returns {Vector|undefined} a force vector (optional)
*/