@adatechnology/game-crowd 0.2.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/index.d.ts +438 -15
- package/dist/index.global.js +2158 -77
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +2093 -77
- package/dist/index.js.map +1 -1
- package/dist/physics-worker.js +580 -0
- package/dist/physics-worker.js.map +1 -0
- package/package.json +9 -9
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
// src/physics/offload/frame.ts
|
|
2
|
+
var F32_INPUTS = ["x", "z", "y", "vx", "vz", "pushX", "pushZ", "mass", "build", "aimX", "aimZ", "driveFactor", "fieldPrev"];
|
|
3
|
+
var U8_INPUTS = ["band", "balance", "state", "type"];
|
|
4
|
+
var F32_OUTPUTS = ["deltaX", "deltaZ", "deltaPushX", "deltaPushZ", "deltaVx", "deltaVz", "field", "shove", "doorLoad"];
|
|
5
|
+
var HEADER = { n: 0, h: 1, playerOn: 2, playerX: 3, playerZ: 4, contacts: 5, maxOverlap: 6, overlapSum: 7, playerLoad: 8, drivers: 9, fieldMax: 10, size: 16 };
|
|
6
|
+
var align8 = (bytes) => Math.ceil(bytes / 8) * 8;
|
|
7
|
+
function createFrame(buffer, shape) {
|
|
8
|
+
const { capacity, cells } = shape;
|
|
9
|
+
let offset = 0;
|
|
10
|
+
const take = (make, bytesPerElement, length) => {
|
|
11
|
+
const view = make(buffer, offset, length);
|
|
12
|
+
offset += align8(length * bytesPerElement);
|
|
13
|
+
return view;
|
|
14
|
+
};
|
|
15
|
+
const f64 = (b, o, l) => new Float64Array(b, o, l);
|
|
16
|
+
const f32 = (b, o, l) => new Float32Array(b, o, l);
|
|
17
|
+
const u8 = (b, o, l) => new Uint8Array(b, o, l);
|
|
18
|
+
const header = take(f64, 8, HEADER.size);
|
|
19
|
+
const floats = {};
|
|
20
|
+
for (const key of [...F32_INPUTS, ...F32_OUTPUTS]) floats[key] = take(f32, 4, capacity);
|
|
21
|
+
const identity = take((b, o, l) => new Uint32Array(b, o, l), 4, capacity);
|
|
22
|
+
const doorCell = take((b, o, l) => new Int32Array(b, o, l), 4, capacity);
|
|
23
|
+
const bytes = {};
|
|
24
|
+
for (const key of U8_INPUTS) bytes[key] = take(u8, 1, capacity);
|
|
25
|
+
const occ = take(u8, 1, cells);
|
|
26
|
+
return { header, f32: floats, u8: bytes, identity, doorCell, occ };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/grid.ts
|
|
30
|
+
var createCellOf = (half, cell, n) => (x, z) => {
|
|
31
|
+
const gx = Math.floor((x + half) / cell);
|
|
32
|
+
const gz = Math.floor((z + half) / cell);
|
|
33
|
+
return gx < 0 || gz < 0 || gx >= n || gz >= n ? -1 : gz * n + gx;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/identity.ts
|
|
37
|
+
var mix32 = (value) => {
|
|
38
|
+
let hash = value >>> 0;
|
|
39
|
+
hash ^= hash >>> 16;
|
|
40
|
+
hash = Math.imul(hash, 2246822507);
|
|
41
|
+
hash ^= hash >>> 13;
|
|
42
|
+
hash = Math.imul(hash, 3266489909);
|
|
43
|
+
hash ^= hash >>> 16;
|
|
44
|
+
return hash >>> 0;
|
|
45
|
+
};
|
|
46
|
+
var unit = (identity, step, salt) => (mix32(mix32(identity ^ salt) + Math.imul(step + 1, 2654435769) >>> 0) >>> 0) / 4294967296;
|
|
47
|
+
var SALT = {
|
|
48
|
+
FALL: 62481,
|
|
49
|
+
TRIP: 29209,
|
|
50
|
+
LIMB: 110848,
|
|
51
|
+
REGION: 189968,
|
|
52
|
+
JAW: 239536,
|
|
53
|
+
CORPSE: 311518,
|
|
54
|
+
SPIN: 364922,
|
|
55
|
+
SPEED: 413981,
|
|
56
|
+
REACTION: 466972,
|
|
57
|
+
STABILITY: 569886,
|
|
58
|
+
TRIP_FACTOR: 605021,
|
|
59
|
+
DRIVE: 655571,
|
|
60
|
+
CADENCE: 728142,
|
|
61
|
+
AGGRESSION: 797335,
|
|
62
|
+
IDLE: 867848,
|
|
63
|
+
GAIT: 937753
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/types.ts
|
|
67
|
+
var STATE = {
|
|
68
|
+
WANDER: 0,
|
|
69
|
+
CHASE: 1,
|
|
70
|
+
GRAB: 2,
|
|
71
|
+
STUNNED: 3,
|
|
72
|
+
CALLING: 4,
|
|
73
|
+
AIRBORNE: 5,
|
|
74
|
+
DORMANT: 6,
|
|
75
|
+
SWELLING: 7,
|
|
76
|
+
SWING: 8,
|
|
77
|
+
EATING: 9,
|
|
78
|
+
PILE_STEP: 10,
|
|
79
|
+
SEEKING_SOUND: 12
|
|
80
|
+
};
|
|
81
|
+
var BALANCE = { STANDING: 0, STAGGERING: 1, FALLEN: 2, GETTING_UP: 3, THROWN: 4 };
|
|
82
|
+
|
|
83
|
+
// src/physics/contact.ts
|
|
84
|
+
var GROUND_MASS_FACTOR = 3;
|
|
85
|
+
var PAIR_MARGIN = 0.15;
|
|
86
|
+
var MAX_LEVEL_GAP = 0.15;
|
|
87
|
+
var PAIRS_PER_AGENT = 12;
|
|
88
|
+
var MIN_DISTANCE = 1e-6;
|
|
89
|
+
var createContactWorkspace = (capacity, grid) => ({
|
|
90
|
+
head: new Int32Array(Math.floor(grid.size) ** 2).fill(-1),
|
|
91
|
+
next: new Int32Array(capacity),
|
|
92
|
+
touched: new Int32Array(capacity),
|
|
93
|
+
touchedCount: 0,
|
|
94
|
+
pairI: new Int32Array(capacity * PAIRS_PER_AGENT),
|
|
95
|
+
pairJ: new Int32Array(capacity * PAIRS_PER_AGENT),
|
|
96
|
+
pairCount: 0,
|
|
97
|
+
deltaX: new Float32Array(capacity),
|
|
98
|
+
deltaZ: new Float32Array(capacity),
|
|
99
|
+
count: new Uint8Array(capacity),
|
|
100
|
+
pushBeforeX: new Float32Array(capacity),
|
|
101
|
+
pushBeforeZ: new Float32Array(capacity),
|
|
102
|
+
shove: new Float32Array(capacity),
|
|
103
|
+
takesPart: new Uint8Array(capacity),
|
|
104
|
+
radiusOf: new Float64Array(capacity),
|
|
105
|
+
massOf: new Float64Array(capacity),
|
|
106
|
+
pairReach: new Float64Array(capacity * PAIRS_PER_AGENT),
|
|
107
|
+
pairDistance: new Float64Array(capacity * PAIRS_PER_AGENT),
|
|
108
|
+
pairNormalX: new Float64Array(capacity * PAIRS_PER_AGENT),
|
|
109
|
+
pairNormalZ: new Float64Array(capacity * PAIRS_PER_AGENT)
|
|
110
|
+
});
|
|
111
|
+
var takesPartInContact = (store, i) => store.band[i] === 0 && store.balance[i] !== BALANCE.THROWN && store.state[i] !== STATE.PILE_STEP && store.state[i] !== STATE.DORMANT;
|
|
112
|
+
var isOnGround = (store, i) => store.balance[i] === BALANCE.FALLEN || store.balance[i] === BALANCE.GETTING_UP;
|
|
113
|
+
var effectiveMass = (store, i) => store.mass[i] * (isOnGround(store, i) ? GROUND_MASS_FACTOR : 1);
|
|
114
|
+
var bodyRadius = (store, i, radius) => radius * store.build[i];
|
|
115
|
+
function readBodies(view, radius) {
|
|
116
|
+
const { store, workspace } = view;
|
|
117
|
+
const { takesPart, radiusOf, massOf } = workspace;
|
|
118
|
+
for (let i = 0; i < view.n; i++) {
|
|
119
|
+
const takes = takesPartInContact(store, i);
|
|
120
|
+
takesPart[i] = takes ? 1 : 0;
|
|
121
|
+
radiusOf[i] = bodyRadius(store, i, radius);
|
|
122
|
+
massOf[i] = effectiveMass(store, i);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function rebuildHash(view) {
|
|
126
|
+
const { store, grid, workspace } = view;
|
|
127
|
+
const side = Math.floor(grid.size);
|
|
128
|
+
for (let k = 0; k < workspace.touchedCount; k++) workspace.head[workspace.touched[k]] = -1;
|
|
129
|
+
workspace.touchedCount = 0;
|
|
130
|
+
for (let i = 0; i < view.n; i++) {
|
|
131
|
+
if (!workspace.takesPart[i]) continue;
|
|
132
|
+
const hx = Math.floor(store.x[i] + grid.half);
|
|
133
|
+
const hz = Math.floor(store.z[i] + grid.half);
|
|
134
|
+
if (hx < 0 || hz < 0 || hx >= side || hz >= side) continue;
|
|
135
|
+
const cell = hz * side + hx;
|
|
136
|
+
if (workspace.head[cell] === -1) workspace.touched[workspace.touchedCount++] = cell;
|
|
137
|
+
workspace.next[i] = workspace.head[cell];
|
|
138
|
+
workspace.head[cell] = i;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
var FORWARD_CELLS = [1, 0, -1, 1, 0, 1, 1, 1];
|
|
142
|
+
function addPairsOfCell(view, i, cell, sameCell) {
|
|
143
|
+
const { store, workspace } = view;
|
|
144
|
+
const { head, next, radiusOf, pairI, pairJ } = workspace;
|
|
145
|
+
const { x, z, y } = store;
|
|
146
|
+
const xi = x[i];
|
|
147
|
+
const zi = z[i];
|
|
148
|
+
const yi = y[i];
|
|
149
|
+
const reachBase = radiusOf[i] + PAIR_MARGIN;
|
|
150
|
+
for (let j = head[cell]; j !== -1; j = next[j]) {
|
|
151
|
+
if (sameCell && j <= i || Math.abs(yi - y[j]) > MAX_LEVEL_GAP) continue;
|
|
152
|
+
const reach = reachBase + radiusOf[j];
|
|
153
|
+
const ex = x[j] - xi;
|
|
154
|
+
const ez = z[j] - zi;
|
|
155
|
+
if (ex * ex + ez * ez >= reach * reach || workspace.pairCount >= pairI.length) continue;
|
|
156
|
+
pairI[workspace.pairCount] = i;
|
|
157
|
+
pairJ[workspace.pairCount++] = j;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function collectPairs(view) {
|
|
161
|
+
const { store, grid, workspace } = view;
|
|
162
|
+
const side = Math.floor(grid.size);
|
|
163
|
+
workspace.pairCount = 0;
|
|
164
|
+
for (let i = 0; i < view.n; i++) {
|
|
165
|
+
if (!workspace.takesPart[i]) continue;
|
|
166
|
+
const hx = Math.floor(store.x[i] + grid.half);
|
|
167
|
+
const hz = Math.floor(store.z[i] + grid.half);
|
|
168
|
+
if (hx < 0 || hz < 0 || hx >= side || hz >= side) continue;
|
|
169
|
+
addPairsOfCell(view, i, hz * side + hx, true);
|
|
170
|
+
for (let k = 0; k < FORWARD_CELLS.length; k += 2) {
|
|
171
|
+
const qx = hx + FORWARD_CELLS[k];
|
|
172
|
+
const qz = hz + FORWARD_CELLS[k + 1];
|
|
173
|
+
if (qx >= 0 && qz >= 0 && qx < side && qz < side) addPairsOfCell(view, i, qz * side + qx, false);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function measurePairs(view) {
|
|
178
|
+
const { store, workspace } = view;
|
|
179
|
+
const { pairI, pairJ, radiusOf, pairReach, pairDistance, pairNormalX, pairNormalZ } = workspace;
|
|
180
|
+
const { x, z, identity } = store;
|
|
181
|
+
for (let p = 0; p < workspace.pairCount; p++) {
|
|
182
|
+
const i = pairI[p];
|
|
183
|
+
const j = pairJ[p];
|
|
184
|
+
const reach = radiusOf[i] + radiusOf[j];
|
|
185
|
+
const ex = x[i] - x[j];
|
|
186
|
+
const ez = z[i] - z[j];
|
|
187
|
+
const distance = Math.sqrt(ex * ex + ez * ez);
|
|
188
|
+
pairReach[p] = reach;
|
|
189
|
+
pairDistance[p] = distance;
|
|
190
|
+
if (distance >= reach) continue;
|
|
191
|
+
if (distance > MIN_DISTANCE) {
|
|
192
|
+
pairNormalX[p] = ex / distance;
|
|
193
|
+
pairNormalZ[p] = ez / distance;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const angle = 2 * Math.PI * unit(identity[i] ^ identity[j], 0, SALT.SPIN);
|
|
197
|
+
pairNormalX[p] = Math.cos(angle);
|
|
198
|
+
pairNormalZ[p] = Math.sin(angle);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function solveImpulse(store, workspace, p) {
|
|
202
|
+
if (workspace.pairDistance[p] >= workspace.pairReach[p]) return;
|
|
203
|
+
const i = workspace.pairI[p];
|
|
204
|
+
const j = workspace.pairJ[p];
|
|
205
|
+
const nx = workspace.pairNormalX[p];
|
|
206
|
+
const nz = workspace.pairNormalZ[p];
|
|
207
|
+
const { vx, vz, pushX, pushZ } = store;
|
|
208
|
+
const closing = (vx[i] + pushX[i] - vx[j] - pushX[j]) * nx + (vz[i] + pushZ[i] - vz[j] - pushZ[j]) * nz;
|
|
209
|
+
if (closing >= 0) return;
|
|
210
|
+
const massI = workspace.massOf[i];
|
|
211
|
+
const massJ = workspace.massOf[j];
|
|
212
|
+
const impulse = -closing * massI * massJ / (massI + massJ);
|
|
213
|
+
pushX[i] += impulse * nx / massI;
|
|
214
|
+
pushZ[i] += impulse * nz / massI;
|
|
215
|
+
pushX[j] -= impulse * nx / massJ;
|
|
216
|
+
pushZ[j] -= impulse * nz / massJ;
|
|
217
|
+
}
|
|
218
|
+
function accumulateCorrection(workspace, p, relaxation) {
|
|
219
|
+
const distance = workspace.pairDistance[p];
|
|
220
|
+
const reach = workspace.pairReach[p];
|
|
221
|
+
if (distance >= reach) return;
|
|
222
|
+
const i = workspace.pairI[p];
|
|
223
|
+
const j = workspace.pairJ[p];
|
|
224
|
+
const massI = workspace.massOf[i];
|
|
225
|
+
const massJ = workspace.massOf[j];
|
|
226
|
+
const move = relaxation * (reach - distance);
|
|
227
|
+
const nx = workspace.pairNormalX[p];
|
|
228
|
+
const nz = workspace.pairNormalZ[p];
|
|
229
|
+
workspace.deltaX[i] += move * nx * (massJ / (massI + massJ));
|
|
230
|
+
workspace.deltaZ[i] += move * nz * (massJ / (massI + massJ));
|
|
231
|
+
workspace.deltaX[j] -= move * nx * (massI / (massI + massJ));
|
|
232
|
+
workspace.deltaZ[j] -= move * nz * (massI / (massI + massJ));
|
|
233
|
+
workspace.count[i]++;
|
|
234
|
+
workspace.count[j]++;
|
|
235
|
+
}
|
|
236
|
+
var isOpen = (grid, x, z) => {
|
|
237
|
+
const cell = grid.cellOf(x, z);
|
|
238
|
+
return cell >= 0 && (grid.occ[cell] === 0 || grid.occ[cell] === 3);
|
|
239
|
+
};
|
|
240
|
+
function applyCorrection(view, agent, params) {
|
|
241
|
+
const { store, grid, workspace } = view;
|
|
242
|
+
const count = workspace.count[agent];
|
|
243
|
+
if (!count) return;
|
|
244
|
+
workspace.count[agent] = 0;
|
|
245
|
+
const cap = params.maxCorrection;
|
|
246
|
+
const dx = Math.max(-cap, Math.min(cap, workspace.deltaX[agent] / count));
|
|
247
|
+
const dz = Math.max(-cap, Math.min(cap, workspace.deltaZ[agent] / count));
|
|
248
|
+
if (isOpen(grid, store.x[agent] + dx, store.z[agent])) store.x[agent] += dx;
|
|
249
|
+
if (isOpen(grid, store.x[agent], store.z[agent] + dz)) store.z[agent] += dz;
|
|
250
|
+
}
|
|
251
|
+
function resetCorrection(workspace, agent) {
|
|
252
|
+
workspace.deltaX[agent] = 0;
|
|
253
|
+
workspace.deltaZ[agent] = 0;
|
|
254
|
+
workspace.count[agent] = 0;
|
|
255
|
+
}
|
|
256
|
+
function correctPositions(view, params) {
|
|
257
|
+
const { workspace } = view;
|
|
258
|
+
for (let p = 0; p < workspace.pairCount; p++) {
|
|
259
|
+
resetCorrection(workspace, workspace.pairI[p]);
|
|
260
|
+
resetCorrection(workspace, workspace.pairJ[p]);
|
|
261
|
+
}
|
|
262
|
+
for (let p = 0; p < workspace.pairCount; p++) accumulateCorrection(workspace, p, params.relaxation);
|
|
263
|
+
for (let p = 0; p < workspace.pairCount; p++) {
|
|
264
|
+
applyCorrection(view, workspace.pairI[p], params);
|
|
265
|
+
applyCorrection(view, workspace.pairJ[p], params);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function measureOverlaps(view, out) {
|
|
269
|
+
const { store, workspace } = view;
|
|
270
|
+
for (let p = 0; p < workspace.pairCount; p++) {
|
|
271
|
+
const i = workspace.pairI[p];
|
|
272
|
+
const j = workspace.pairJ[p];
|
|
273
|
+
if (isOnGround(store, i) && isOnGround(store, j)) continue;
|
|
274
|
+
const reach = workspace.radiusOf[i] + workspace.radiusOf[j];
|
|
275
|
+
const overlap = reach - Math.hypot(store.x[i] - store.x[j], store.z[i] - store.z[j]);
|
|
276
|
+
if (overlap <= 0) continue;
|
|
277
|
+
out.contacts++;
|
|
278
|
+
out.overlapSum += overlap;
|
|
279
|
+
if (overlap > out.maxOverlap) out.maxOverlap = overlap;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function dampPush(view, params) {
|
|
283
|
+
const { store, workspace } = view;
|
|
284
|
+
const lambda = Math.exp(-params.friction * params.h);
|
|
285
|
+
for (let i = 0; i < view.n; i++) {
|
|
286
|
+
workspace.shove[i] = Math.hypot(store.pushX[i] - workspace.pushBeforeX[i], store.pushZ[i] - workspace.pushBeforeZ[i]);
|
|
287
|
+
store.pushX[i] *= lambda;
|
|
288
|
+
store.pushZ[i] *= lambda;
|
|
289
|
+
const speed = Math.hypot(store.pushX[i], store.pushZ[i]);
|
|
290
|
+
if (speed <= params.maxPush) continue;
|
|
291
|
+
store.pushX[i] *= params.maxPush / speed;
|
|
292
|
+
store.pushZ[i] *= params.maxPush / speed;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function contactStep(view, params, out) {
|
|
296
|
+
const { store, workspace } = view;
|
|
297
|
+
out.contacts = 0;
|
|
298
|
+
out.maxOverlap = 0;
|
|
299
|
+
out.overlapSum = 0;
|
|
300
|
+
for (let i = 0; i < view.n; i++) {
|
|
301
|
+
workspace.pushBeforeX[i] = store.pushX[i];
|
|
302
|
+
workspace.pushBeforeZ[i] = store.pushZ[i];
|
|
303
|
+
}
|
|
304
|
+
readBodies(view, params.radius);
|
|
305
|
+
rebuildHash(view);
|
|
306
|
+
collectPairs(view);
|
|
307
|
+
for (let iteration = 0; iteration < params.iterations; iteration++) {
|
|
308
|
+
measurePairs(view);
|
|
309
|
+
for (let p = 0; p < workspace.pairCount; p++) solveImpulse(store, workspace, p);
|
|
310
|
+
for (let p = workspace.pairCount - 1; p >= 0; p--) solveImpulse(store, workspace, p);
|
|
311
|
+
correctPositions(view, params);
|
|
312
|
+
}
|
|
313
|
+
measureOverlaps(view, out);
|
|
314
|
+
dampPush(view, params);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/physics/pressure.ts
|
|
318
|
+
var TOUCH_SLACK = 0.08;
|
|
319
|
+
var WALL_PROBE = 0.12;
|
|
320
|
+
var PLAYER_REACH = 1.3;
|
|
321
|
+
var PLAYER_RADIUS_SQUARED = PLAYER_REACH * PLAYER_REACH;
|
|
322
|
+
var createPressureWorkspace = (capacity, pairCapacity) => ({
|
|
323
|
+
weightSum: new Float32Array(capacity),
|
|
324
|
+
pressing: new Uint8Array(capacity),
|
|
325
|
+
wallKind: new Uint8Array(capacity),
|
|
326
|
+
wallDoor: new Int32Array(capacity),
|
|
327
|
+
playerWeight: new Float32Array(capacity),
|
|
328
|
+
cutExcess: new Float32Array(capacity),
|
|
329
|
+
cutX: new Float32Array(capacity),
|
|
330
|
+
cutZ: new Float32Array(capacity),
|
|
331
|
+
pairWeightForward: new Float32Array(pairCapacity),
|
|
332
|
+
pairWeightBackward: new Float32Array(pairCapacity),
|
|
333
|
+
canPress: new Uint8Array(capacity)
|
|
334
|
+
});
|
|
335
|
+
var isPressing = (store, i) => takesPartInContact(store, i) && (store.balance[i] === BALANCE.STANDING || store.balance[i] === BALANCE.STAGGERING);
|
|
336
|
+
function readPressing(view, workspace) {
|
|
337
|
+
for (let i = 0; i < view.n; i++) workspace.canPress[i] = isPressing(view.store, i) ? 1 : 0;
|
|
338
|
+
}
|
|
339
|
+
var frontWeight = (alignment, cone) => alignment > cone ? (alignment - cone) / (1 - cone) : 0;
|
|
340
|
+
var hasAim = (store, i) => store.aimX[i] !== 0 || store.aimZ[i] !== 0;
|
|
341
|
+
function noteCut(store, workspace, i, j, nx, nz) {
|
|
342
|
+
const excess = (store.vx[i] - store.vx[j]) * nx + (store.vz[i] - store.vz[j]) * nz;
|
|
343
|
+
if (excess <= workspace.cutExcess[i]) return;
|
|
344
|
+
workspace.cutExcess[i] = excess;
|
|
345
|
+
workspace.cutX[i] = nx;
|
|
346
|
+
workspace.cutZ[i] = nz;
|
|
347
|
+
}
|
|
348
|
+
function weighPairs(view, workspace, params) {
|
|
349
|
+
const { store } = view;
|
|
350
|
+
for (let p = 0; p < view.workspace.pairCount; p++) {
|
|
351
|
+
workspace.pairWeightForward[p] = 0;
|
|
352
|
+
workspace.pairWeightBackward[p] = 0;
|
|
353
|
+
const i = view.workspace.pairI[p];
|
|
354
|
+
const j = view.workspace.pairJ[p];
|
|
355
|
+
if (!workspace.canPress[i] || !workspace.canPress[j]) continue;
|
|
356
|
+
const ex = store.x[j] - store.x[i];
|
|
357
|
+
const ez = store.z[j] - store.z[i];
|
|
358
|
+
const distance = Math.sqrt(ex * ex + ez * ez);
|
|
359
|
+
if (distance < 1e-6 || distance > (store.build[i] + store.build[j]) * params.radius + TOUCH_SLACK) continue;
|
|
360
|
+
const nx = ex / distance;
|
|
361
|
+
const nz = ez / distance;
|
|
362
|
+
const forward = frontWeight(store.aimX[i] * nx + store.aimZ[i] * nz, params.frontCone);
|
|
363
|
+
const backward = frontWeight(-(store.aimX[j] * nx + store.aimZ[j] * nz), params.frontCone);
|
|
364
|
+
if (forward > 0) {
|
|
365
|
+
workspace.pressing[i] = 1;
|
|
366
|
+
noteCut(store, workspace, i, j, nx, nz);
|
|
367
|
+
}
|
|
368
|
+
if (backward > 0) {
|
|
369
|
+
workspace.pressing[j] = 1;
|
|
370
|
+
noteCut(store, workspace, j, i, -nx, -nz);
|
|
371
|
+
}
|
|
372
|
+
if (forward > 0 && backward > 0) continue;
|
|
373
|
+
workspace.pairWeightForward[p] = forward;
|
|
374
|
+
workspace.pairWeightBackward[p] = backward;
|
|
375
|
+
workspace.weightSum[i] += forward;
|
|
376
|
+
workspace.weightSum[j] += backward;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function weighWalls(view, workspace, params, doorAt) {
|
|
380
|
+
const { store, grid } = view;
|
|
381
|
+
for (let i = 0; i < view.n; i++) {
|
|
382
|
+
if (!workspace.canPress[i] || !hasAim(store, i)) continue;
|
|
383
|
+
const reach = store.build[i] * params.radius + WALL_PROBE;
|
|
384
|
+
const cell = grid.cellOf(store.x[i] + store.aimX[i] * reach, store.z[i] + store.aimZ[i] * reach);
|
|
385
|
+
if (cell >= 0 && (grid.occ[cell] === 0 || grid.occ[cell] === 3)) continue;
|
|
386
|
+
workspace.wallKind[i] = 1;
|
|
387
|
+
workspace.wallDoor[i] = cell >= 0 && grid.occ[cell] === 5 ? doorAt(cell) : -1;
|
|
388
|
+
workspace.pressing[i] = 1;
|
|
389
|
+
workspace.weightSum[i] += 1;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function weighPlayer(view, workspace, params, player) {
|
|
393
|
+
const { store } = view;
|
|
394
|
+
for (let i = 0; i < view.n; i++) {
|
|
395
|
+
if (!workspace.canPress[i]) continue;
|
|
396
|
+
const ex = player.x - store.x[i];
|
|
397
|
+
const ez = player.z - store.z[i];
|
|
398
|
+
const squared = ex * ex + ez * ez;
|
|
399
|
+
if (squared > PLAYER_RADIUS_SQUARED || squared < 1e-6) continue;
|
|
400
|
+
const distance = Math.sqrt(squared);
|
|
401
|
+
const weight = hasAim(store, i) ? frontWeight((store.aimX[i] * ex + store.aimZ[i] * ez) / distance, params.frontCone) : 1;
|
|
402
|
+
workspace.playerWeight[i] = weight;
|
|
403
|
+
if (weight > 0) workspace.pressing[i] = 1;
|
|
404
|
+
workspace.weightSum[i] += weight;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function clearWorkspace(view, workspace) {
|
|
408
|
+
const { store } = view;
|
|
409
|
+
for (let i = 0; i < view.n; i++) {
|
|
410
|
+
workspace.weightSum[i] = 0;
|
|
411
|
+
workspace.pressing[i] = 0;
|
|
412
|
+
workspace.wallKind[i] = 0;
|
|
413
|
+
workspace.playerWeight[i] = 0;
|
|
414
|
+
workspace.cutExcess[i] = 0;
|
|
415
|
+
store.field[i] = 0;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function ownDrive(store, context, workspace, i) {
|
|
419
|
+
if (!hasAim(store, i) || !workspace.pressing[i]) return 0;
|
|
420
|
+
return context.driveForceOfType[store.type[i]] * store.driveFactor[i];
|
|
421
|
+
}
|
|
422
|
+
function spreadField(view, workspace, params, context, out) {
|
|
423
|
+
const { store } = view;
|
|
424
|
+
for (let i = 0; i < view.n; i++) {
|
|
425
|
+
if (!workspace.canPress[i]) continue;
|
|
426
|
+
store.field[i] = ownDrive(store, context, workspace, i);
|
|
427
|
+
if (store.field[i] > 0) out.drivers++;
|
|
428
|
+
}
|
|
429
|
+
for (let p = 0; p < view.workspace.pairCount; p++) {
|
|
430
|
+
const i = view.workspace.pairI[p];
|
|
431
|
+
const j = view.workspace.pairJ[p];
|
|
432
|
+
const forward = workspace.pairWeightForward[p];
|
|
433
|
+
const backward = workspace.pairWeightBackward[p];
|
|
434
|
+
if (forward > 0) store.field[j] += params.transmission * store.fieldPrev[i] * forward * forward / workspace.weightSum[i];
|
|
435
|
+
if (backward > 0) store.field[i] += params.transmission * store.fieldPrev[j] * backward * backward / workspace.weightSum[j];
|
|
436
|
+
}
|
|
437
|
+
for (let i = 0; i < view.n; i++) {
|
|
438
|
+
if (!workspace.canPress[i]) continue;
|
|
439
|
+
const passed = params.transmission * store.fieldPrev[i] / (workspace.weightSum[i] || 1);
|
|
440
|
+
if (workspace.wallKind[i] && workspace.wallDoor[i] >= 0) {
|
|
441
|
+
if (context.doorOut) {
|
|
442
|
+
context.doorOut.cell[i] = workspace.wallDoor[i];
|
|
443
|
+
context.doorOut.load[i] = passed * params.h;
|
|
444
|
+
} else context.doorLoad[workspace.wallDoor[i]] += passed * params.h;
|
|
445
|
+
}
|
|
446
|
+
if (workspace.playerWeight[i] > 0) out.playerLoad += passed * workspace.playerWeight[i] * workspace.playerWeight[i];
|
|
447
|
+
if (store.field[i] > params.cap) store.field[i] = params.cap;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function applyCuts(view, workspace) {
|
|
451
|
+
const { store } = view;
|
|
452
|
+
for (let i = 0; i < view.n; i++) {
|
|
453
|
+
const excess = workspace.cutExcess[i];
|
|
454
|
+
if (excess <= 0) continue;
|
|
455
|
+
store.vx[i] -= excess * workspace.cutX[i];
|
|
456
|
+
store.vz[i] -= excess * workspace.cutZ[i];
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function pressureStep(view, workspace, params, context, out) {
|
|
460
|
+
out.playerLoad = 0;
|
|
461
|
+
out.drivers = 0;
|
|
462
|
+
clearWorkspace(view, workspace);
|
|
463
|
+
readPressing(view, workspace);
|
|
464
|
+
weighPairs(view, workspace, params);
|
|
465
|
+
weighWalls(view, workspace, params, context.doorAt);
|
|
466
|
+
if (context.player) weighPlayer(view, workspace, params, context.player);
|
|
467
|
+
spreadField(view, workspace, params, context, out);
|
|
468
|
+
applyCuts(view, workspace);
|
|
469
|
+
for (let i = 0; i < view.n; i++) view.store.fieldPrev[i] = view.store.field[i];
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/physics/offload/kernel.ts
|
|
473
|
+
var PAIRS_PER_AGENT2 = 12;
|
|
474
|
+
var STORE_KEYS = ["x", "z", "y", "vx", "vz", "pushX", "pushZ", "mass", "build", "aimX", "aimZ", "driveFactor", "fieldPrev"];
|
|
475
|
+
function createKernel(setup) {
|
|
476
|
+
const { capacity } = setup;
|
|
477
|
+
const side = Math.floor(setup.gridSize / setup.gridCell);
|
|
478
|
+
const half = setup.gridSize / 2;
|
|
479
|
+
const before = { x: new Float32Array(capacity), z: new Float32Array(capacity), pushX: new Float32Array(capacity), pushZ: new Float32Array(capacity), vx: new Float32Array(capacity), vz: new Float32Array(capacity) };
|
|
480
|
+
const driveForceOfType = Float32Array.from(setup.driveForceOfType);
|
|
481
|
+
const contactResult = { contacts: 0, maxOverlap: 0, overlapSum: 0 };
|
|
482
|
+
const pressureResult = { playerLoad: 0, drivers: 0 };
|
|
483
|
+
const workspace = setup.pressure ? createPressureWorkspace(capacity, capacity * PAIRS_PER_AGENT2) : null;
|
|
484
|
+
const cellOf = createCellOf(half, setup.gridCell, side);
|
|
485
|
+
const doorLoadUnused = new Float32Array(0);
|
|
486
|
+
const store = {};
|
|
487
|
+
const grid = { size: setup.gridSize, half, occ: new Uint8Array(0), cellOf };
|
|
488
|
+
const view = { n: 0, store, grid, workspace: createContactWorkspace(capacity, grid) };
|
|
489
|
+
const bind = (frame) => {
|
|
490
|
+
Object.assign(store, frame.u8, { identity: frame.identity, field: frame.f32.field });
|
|
491
|
+
for (const key of STORE_KEYS) store[key] = frame.f32[key];
|
|
492
|
+
grid.occ = frame.occ;
|
|
493
|
+
return view;
|
|
494
|
+
};
|
|
495
|
+
const run = (frame) => {
|
|
496
|
+
const { header } = frame;
|
|
497
|
+
const n = header[HEADER.n];
|
|
498
|
+
const contact = bind(frame);
|
|
499
|
+
contact.n = n;
|
|
500
|
+
for (let i = 0; i < n; i++) {
|
|
501
|
+
before.x[i] = frame.f32.x[i];
|
|
502
|
+
before.z[i] = frame.f32.z[i];
|
|
503
|
+
before.pushX[i] = frame.f32.pushX[i];
|
|
504
|
+
before.pushZ[i] = frame.f32.pushZ[i];
|
|
505
|
+
before.vx[i] = frame.f32.vx[i];
|
|
506
|
+
before.vz[i] = frame.f32.vz[i];
|
|
507
|
+
}
|
|
508
|
+
const h = header[HEADER.h];
|
|
509
|
+
const params = { ...setup.contact, h };
|
|
510
|
+
contactStep(contact, params, contactResult);
|
|
511
|
+
header[HEADER.contacts] = contactResult.contacts;
|
|
512
|
+
header[HEADER.maxOverlap] = contactResult.maxOverlap;
|
|
513
|
+
header[HEADER.overlapSum] = contactResult.overlapSum;
|
|
514
|
+
frame.doorCell.fill(-1, 0, n);
|
|
515
|
+
frame.f32.doorLoad.fill(0, 0, n);
|
|
516
|
+
if (workspace && setup.pressure) {
|
|
517
|
+
const player = header[HEADER.playerOn] ? { x: header[HEADER.playerX], z: header[HEADER.playerZ] } : void 0;
|
|
518
|
+
const doorOut = { cell: frame.doorCell, load: frame.f32.doorLoad };
|
|
519
|
+
pressureStep(contact, workspace, { ...setup.pressure, h, radius: setup.contact.radius }, { driveForceOfType, doorAt: (cell) => cell, doorLoad: doorLoadUnused, player, doorOut }, pressureResult);
|
|
520
|
+
let top = 0;
|
|
521
|
+
for (let i = 0; i < n; i++) top = Math.max(top, frame.f32.field[i]);
|
|
522
|
+
header[HEADER.fieldMax] = top;
|
|
523
|
+
} else header[HEADER.fieldMax] = 0;
|
|
524
|
+
header[HEADER.playerLoad] = pressureResult.playerLoad;
|
|
525
|
+
header[HEADER.drivers] = pressureResult.drivers;
|
|
526
|
+
const { f32 } = frame;
|
|
527
|
+
for (let i = 0; i < n; i++) {
|
|
528
|
+
f32.deltaX[i] = f32.x[i] - before.x[i];
|
|
529
|
+
f32.deltaZ[i] = f32.z[i] - before.z[i];
|
|
530
|
+
f32.deltaPushX[i] = f32.pushX[i] - before.pushX[i];
|
|
531
|
+
f32.deltaPushZ[i] = f32.pushZ[i] - before.pushZ[i];
|
|
532
|
+
f32.deltaVx[i] = f32.vx[i] - before.vx[i];
|
|
533
|
+
f32.deltaVz[i] = f32.vz[i] - before.vz[i];
|
|
534
|
+
f32.shove[i] = contact.workspace.shove[i];
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
return { run };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/physics/offload/worker-host.ts
|
|
541
|
+
var CONTROL = { IDLE: 0, SUBMITTED: 1, DONE: 2, STOP: 3 };
|
|
542
|
+
function attachPhysicsWorker(port) {
|
|
543
|
+
let kernel = null;
|
|
544
|
+
let shape = null;
|
|
545
|
+
port.onMessage((data) => {
|
|
546
|
+
const message = data;
|
|
547
|
+
if (message.type === "init") {
|
|
548
|
+
kernel = createKernel(message.setup);
|
|
549
|
+
shape = message.shape;
|
|
550
|
+
if (message.frames && message.control) runShared(kernel, message.frames.map((buffer) => createFrame(buffer, message.shape)), new Int32Array(message.control));
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
if (!kernel || !shape) return;
|
|
554
|
+
const frame = createFrame(message.buffer, shape);
|
|
555
|
+
kernel.run(frame);
|
|
556
|
+
port.postMessage({ type: "done", buffer: message.buffer }, [message.buffer]);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
function runShared(kernel, frames, control) {
|
|
560
|
+
for (let turn = 0; ; turn++) {
|
|
561
|
+
const slot = turn % frames.length;
|
|
562
|
+
for (; ; ) {
|
|
563
|
+
const state = Atomics.load(control, slot);
|
|
564
|
+
if (state === CONTROL.STOP) return;
|
|
565
|
+
if (state === CONTROL.SUBMITTED) break;
|
|
566
|
+
Atomics.wait(control, slot, state);
|
|
567
|
+
}
|
|
568
|
+
kernel.run(frames[slot]);
|
|
569
|
+
Atomics.store(control, slot, CONTROL.DONE);
|
|
570
|
+
Atomics.notify(control, slot);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/physics/offload/worker-entry.ts
|
|
575
|
+
var scope = globalThis;
|
|
576
|
+
if (typeof scope.WorkerGlobalScope !== "undefined" && scope.postMessage && scope.addEventListener) {
|
|
577
|
+
const self = scope;
|
|
578
|
+
attachPhysicsWorker({ postMessage: (message, transfer) => self.postMessage(message, transfer), onMessage: (listener) => self.addEventListener("message", (event) => listener(event.data)) });
|
|
579
|
+
}
|
|
580
|
+
//# sourceMappingURL=physics-worker.js.map
|