q5play 4.0.7 → 4.1.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.
Files changed (4) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  3. package/q5play.d.ts +163 -66
  4. package/q5play.js +217 -129
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Visit [q5play.org][]! 🎮🧑‍💻
4
4
 
5
- [q5play][] is a beginner friendly, powerful, and cutting edge game engine for the web.
5
+ [q5play][] is a beginner friendly and powerful game engine for the web.
6
6
 
7
7
  It uses [q5.js WebGPU][] for graphics and [Box2D v3 WASM][] for physics.
8
8
 
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "q5play",
3
- "version": "4.0.7",
3
+ "version": "4.1.0",
4
4
  "author": "quinton-ashley",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
- "description": "A web-based game engine that uses q5.js WebGPU for graphics and Box2D v3 WASM for physics.",
6
+ "description": "A beginner friendly, web-based game engine that uses q5.js WebGPU for graphics and Box2D v3 WASM for physics.",
7
7
  "main": "q5play.js",
8
8
  "types": "q5play.d.ts",
9
9
  "homepage": "https://q5play.org",
@@ -25,6 +25,6 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "box2d3-wasm": "^5.2.0",
28
- "q5": "^4.4.4"
28
+ "q5": "^4.6.3"
29
29
  }
30
30
  }
package/q5play.d.ts CHANGED
@@ -33,10 +33,10 @@ declare global {
33
33
  * Friendly rounding makes some Sprite getters return nice rounded numbers
34
34
  * if a decimal value is within linear slop range (+/-0.005) or
35
35
  * angular slop range (+/-0.000582 radians) of a whole number.
36
- *
36
+ *
37
37
  * This is because Box2D physics calculations can result in
38
38
  * floating point drift, which beginners wouldn't expect.
39
- *
39
+ *
40
40
  * Setting to false can slightly improve performance.
41
41
  * @default true
42
42
  */
@@ -70,6 +70,11 @@ declare global {
70
70
  * @default false
71
71
  */
72
72
  renderStats: boolean;
73
+ /**
74
+ * "Made with q5play" [splash screen](https://en.wikipedia.org/wiki/Splash_screen) displayed during
75
+ * initial page load by default.
76
+ */
77
+ splashScreen(): Promise<void>;
73
78
  /**
74
79
  * Runs automatically before each draw function call.
75
80
  */
@@ -81,6 +86,16 @@ declare global {
81
86
  }
82
87
  const q5play: Q5Play;
83
88
 
89
+ /**
90
+ * Box2D v3 ported to WASM is the physics engine that
91
+ * q5play uses for its physics simulation.
92
+ *
93
+ * This variable enables direct access to the Box2D API for
94
+ * advanced users who want to do things that aren't wrapped
95
+ * by q5play.
96
+ */
97
+ const Box2D: any;
98
+
84
99
  /**
85
100
  * Don't create Shapes directly; use `sprite.addCollider()`
86
101
  * or `sprite.addSensor()` instead.
@@ -107,7 +122,7 @@ declare global {
107
122
  }
108
123
 
109
124
  /**
110
- * Sensor are added to a sprite's physics body to detect overlaps
125
+ * Sensor are added to a sprite's physics body to detect overlaps
111
126
  * without causing physical collisions.
112
127
  *
113
128
  * Don't create Sensors directly; use Sprite.addSensor() instead.
@@ -191,7 +206,7 @@ declare global {
191
206
  * - "!" plays it backwards
192
207
  * - ">" or "<" horizontally flips it
193
208
  * - "^" vertically flips it
194
- *
209
+ *
195
210
  * @param name the name of the animation to play
196
211
  * @returns A promise that fulfills when the animation completes
197
212
  */
@@ -1209,7 +1224,7 @@ declare global {
1209
1224
  }
1210
1225
  /**
1211
1226
  * Stores animations.
1212
- *
1227
+ *
1213
1228
  * Used internally to create `sprite.anis` and `group.anis`.
1214
1229
  *
1215
1230
  * In instances of this class, the keys are animation names,
@@ -1224,7 +1239,7 @@ declare global {
1224
1239
  /**
1225
1240
  * Cuts sprite sheet frames into separate images, instead of rendering
1226
1241
  * sections of the sprite sheet.
1227
- *
1242
+ *
1228
1243
  * Avoids edge bleeding artifacts caused by rotation and scaling,
1229
1244
  * but uses more memory and may cause longer load times.
1230
1245
  */
@@ -2081,7 +2096,7 @@ declare global {
2081
2096
  get reactionTorque(): any;
2082
2097
  /**
2083
2098
  * The amount of force that must be applied to the joint before it breaks.
2084
- *
2099
+ *
2085
2100
  * Setting the threshold too high leads to instability. Use
2086
2101
  * `sprite.addCollider` to simulate unbreakable bonds between shapes.
2087
2102
  * @default 500
@@ -2100,7 +2115,7 @@ declare global {
2100
2115
  /**
2101
2116
  * This function is run when the joint's reaction force exceeds the
2102
2117
  * force threshold or its reaction torque exceeds the torque threshold.
2103
- *
2118
+ *
2104
2119
  * By default, the sprites' speed and rotation speed are set to 0
2105
2120
  * and the joint is deleted, simulating a break.
2106
2121
  */
@@ -2142,7 +2157,7 @@ declare global {
2142
2157
  get currentLength(): number;
2143
2158
  /**
2144
2159
  * The target length of the joint between the two joint anchors.
2145
- *
2160
+ *
2146
2161
  * It's set to the current distance between the two sprites
2147
2162
  * when the joint is created.
2148
2163
  */
@@ -2164,9 +2179,10 @@ declare global {
2164
2179
  */
2165
2180
  get maxLength(): number;
2166
2181
  /**
2167
- * Accepts an array that contains the minimum and maximum length limits.
2182
+ * Accepts a number to set a symmetric range
2183
+ * or an array with the minimum and maximum length limits.
2168
2184
  */
2169
- set range(val: [number, number]);
2185
+ set range(val: [number, number] | number);
2170
2186
  /**
2171
2187
  * Whether spring behavior is enabled for the joint.
2172
2188
  * @default true
@@ -2184,7 +2200,7 @@ declare global {
2184
2200
  /**
2185
2201
  * Damping is a 0-1 ratio describing how quickly the joint loses
2186
2202
  * vibrational energy.
2187
- *
2203
+ *
2188
2204
  * 0.0 means no damping, 1.0 means critical damping, which will stop
2189
2205
  * the joint from vibrating at all.
2190
2206
  *
@@ -2238,39 +2254,37 @@ declare global {
2238
2254
  get angle(): number;
2239
2255
  set angle(val: number);
2240
2256
  /**
2241
- * The current distance between the two joint anchors.
2242
- * @readonly
2243
- */
2244
- get currentLength(): number;
2245
- /**
2246
- * The target length of the joint between the two joint anchors.
2247
- *
2248
- * It's set to the current distance between the two sprites
2249
- * when the joint is created.
2250
- */
2251
- get length(): number;
2252
- set length(val: number);
2253
- /**
2254
- * Whether the joint's length limits are enabled.
2255
- * When enabled a min/max length range constrains the joint.
2257
+ * Whether the joint's suspension limits are enabled.
2258
+ * When enabled a min/max distance from resting constrains the joint.
2256
2259
  * @default false
2257
2260
  */
2258
2261
  get limitsEnabled(): boolean;
2259
2262
  set limitsEnabled(val: boolean);
2260
2263
  /**
2261
- * The minimum length allowed when limits are enabled.
2264
+ * The minimum distance the wheel's suspension can contract
2265
+ * from 0, which represents the resting position,
2266
+ * when limits are enabled.
2267
+ * @readonly
2262
2268
  */
2263
- get minLength(): number;
2269
+ get lowerLimit(): number;
2264
2270
  /**
2265
- * The maximum length allowed when limits are enabled.
2271
+ * The maximum distance the wheel's suspension can extend
2272
+ * from 0, which represents the resting position,
2273
+ * when limits are enabled.
2274
+ * @readonly
2266
2275
  */
2267
- get maxLength(): number;
2276
+ get upperLimit(): number;
2268
2277
  /**
2269
- * Accepts an array that contains the minimum and maximum length limits.
2278
+ * The distance the wheel's suspension can contract or extend
2279
+ * from 0, which represents the resting position.
2280
+ *
2281
+ * Accepts a number to set a symmetric range
2282
+ * or an array with the minimum and maximum length limits.
2270
2283
  */
2271
- set range(val: [number, number]);
2284
+ set range(val: [number, number] | number);
2272
2285
  /**
2273
- * Whether spring behavior is enabled for the joint.
2286
+ * Whether the wheel joint has suspension,
2287
+ * which can make it ride smoother over bumps.
2274
2288
  * @default true
2275
2289
  */
2276
2290
  get springEnabled(): boolean;
@@ -2325,7 +2339,7 @@ declare global {
2325
2339
  * Hinge joints attach two sprites together at a pivot point,
2326
2340
  * constraining them to rotate around this point, like a hinge.
2327
2341
  *
2328
- * A known as a revolute joint.
2342
+ * Also known as a revolute joint.
2329
2343
  *
2330
2344
  * @param spriteA
2331
2345
  * @param spriteB
@@ -2349,13 +2363,57 @@ declare global {
2349
2363
  */
2350
2364
  get maxAngle(): number;
2351
2365
  /**
2352
- * Accepts an array that contains the lower and upper limits of rotation.
2366
+ * Accepts a number to set a symmetric range
2367
+ * or an array with the lower and upper limits of rotation.
2353
2368
  */
2354
- set range(val: [number, number]);
2369
+ set range(val: [number, number] | number);
2355
2370
  /**
2356
- * Read only. The joint's current angle of rotation.
2371
+ * The joint's current angle of rotation.
2372
+ * @readonly
2357
2373
  */
2358
2374
  get angle(): number;
2375
+ /**
2376
+ * Whether spring behavior is enabled.
2377
+ * @default false
2378
+ */
2379
+ get springEnabled(): boolean;
2380
+ set springEnabled(val: boolean);
2381
+ /**
2382
+ * The springiness of the joint, a 0-1 ratio.
2383
+ *
2384
+ * 0 is rigid, 0.5 is bouncy, 1 is loose.
2385
+ * @default 0
2386
+ */
2387
+ get springiness(): number;
2388
+ set springiness(val: number);
2389
+ /**
2390
+ * Damping ratio, 0-1. Higher values reduce oscillation faster.
2391
+ * @default 0
2392
+ */
2393
+ get damping(): number;
2394
+ set damping(val: number);
2395
+ /**
2396
+ * Whether the joint's motor is enabled.
2397
+ * @default false
2398
+ */
2399
+ get motorEnabled(): boolean;
2400
+ set motorEnabled(val: boolean);
2401
+ /**
2402
+ * Motor speed.
2403
+ * @default 0
2404
+ */
2405
+ get speed(): number;
2406
+ set speed(val: number);
2407
+ /**
2408
+ * Maximum torque the motor can apply.
2409
+ */
2410
+ get maxPower(): number;
2411
+ set maxPower(val: number);
2412
+ /**
2413
+ * The current torque being applied by the motor.
2414
+ * @readonly
2415
+ */
2416
+ get power(): number;
2359
2417
  }
2360
2418
 
2361
2419
  class SliderJoint extends Joint {
@@ -2370,44 +2428,83 @@ declare global {
2370
2428
  */
2371
2429
  constructor(spriteA: Sprite, spriteB: Sprite);
2372
2430
  /**
2373
- * The joint's range of translation. Setting the range
2374
- * changes the joint's upper and lower limits.
2375
- * @default undefined
2431
+ * The current displacement of spriteB along the slide axis.
2432
+ * @readonly
2376
2433
  */
2377
- get range(): number;
2378
- set range(val: number);
2434
+ get translation(): number;
2379
2435
  /**
2380
- * The mathematical upper (not positionally higher)
2381
- * limit of translation.
2382
- * @default undefined
2436
+ * Whether the joint's translation limits are enabled.
2437
+ * @default false
2383
2438
  */
2384
- get upperLimit(): number;
2385
- set upperLimit(val: number);
2439
+ get limitsEnabled(): boolean;
2440
+ set limitsEnabled(val: boolean);
2386
2441
  /**
2387
- * The mathematical lower (not positionally lower)
2388
- * limit of translation.
2389
- * @default undefined
2442
+ * The mathematical lower limit of translation.
2443
+ * @readonly
2390
2444
  */
2391
2445
  get lowerLimit(): number;
2392
- set lowerLimit(val: number);
2393
- }
2394
-
2395
- class RopeJoint extends Joint {
2396
2446
  /**
2397
- * A Rope joint prevents two sprites from going further
2398
- * than a certain distance from each other, which is
2399
- * defined by the max length of the rope, but they do allow
2400
- * the sprites to get closer together.
2447
+ * The mathematical upper limit of translation.
2448
+ * @readonly
2449
+ */
2450
+ get upperLimit(): number;
2451
+ /**
2452
+ * Accepts a number to set a symmetric range
2453
+ * or an array with the lower and upper translation limits.
2454
+ */
2455
+ set range(val: [number, number] | number);
2456
+ /**
2457
+ * Alias for range.
2458
+ */
2459
+ set limits(val: [number, number] | number);
2460
+ /**
2461
+ * Whether spring behavior is enabled.
2462
+ * @default false
2463
+ */
2464
+ get springEnabled(): boolean;
2465
+ set springEnabled(val: boolean);
2466
+ /**
2467
+ * The springiness of the joint, a 0-1 ratio.
2401
2468
  *
2402
- * @param spriteA
2403
- * @param spriteB
2469
+ * 0 is rigid, 0.5 is bouncy, 1 is loose.
2470
+ * @default 0
2404
2471
  */
2405
- constructor(spriteA: Sprite, spriteB: Sprite);
2472
+ get springiness(): number;
2473
+ set springiness(val: number);
2406
2474
  /**
2407
- * The maximum length of the rope.
2475
+ * Damping ratio, 0-1. Higher values reduce oscillation faster.
2476
+ * @default 0
2408
2477
  */
2409
- get maxLength(): number;
2410
- set maxLength(val: number);
2478
+ get damping(): number;
2479
+ set damping(val: number);
2480
+ /**
2481
+ * Whether the joint's motor is enabled.
2482
+ * @default true
2483
+ */
2484
+ get motorEnabled(): boolean;
2485
+ set motorEnabled(val: boolean);
2486
+ /**
2487
+ * Motor speed.
2488
+ * @default 0
2489
+ */
2490
+ get speed(): number;
2491
+ set speed(val: number);
2492
+ /**
2493
+ * Maximum force the motor can apply.
2494
+ * @default 10
2495
+ */
2496
+ get maxPower(): number;
2497
+ set maxPower(val: number);
2498
+ /**
2499
+ * The current motor force being applied.
2500
+ * @readonly
2501
+ */
2502
+ get power(): number;
2503
+ /**
2504
+ * The current sliding speed of the joint.
2505
+ * @readonly
2506
+ */
2507
+ get energy(): number;
2411
2508
  }
2412
2509
 
2413
2510
  class GrabberJoint extends Joint {
@@ -2431,7 +2528,7 @@ declare global {
2431
2528
  set target(pos: any);
2432
2529
  /**
2433
2530
  * The maximum spring force that the joint can exert on the sprite.
2434
- *
2531
+ *
2435
2532
  * By default it's 500 * the sprite's mass.
2436
2533
  */
2437
2534
  get maxForce(): number;
package/q5play.js CHANGED
@@ -12,13 +12,12 @@
12
12
  * |__/ |__/ \______/
13
13
  *
14
14
  * @package q5play
15
- * @version 4.0
15
+ * @version 4.1
16
16
  * @author quinton-ashley
17
17
  * @website https://q5play.org
18
18
  */
19
19
 
20
- // will use semver minor after v4 is released
21
- let q5play_version = '4.0';
20
+ let q5play_version = '4.1';
22
21
 
23
22
  if (typeof globalThis.Q5 == 'undefined') {
24
23
  console.error('q5play requires q5.js to be loaded first. Visit https://q5js.org to learn more.');
@@ -324,6 +323,43 @@ async function q5playPreSetup(q) {
324
323
  set friendlyRounding(val) {
325
324
  friendlyRounding = val;
326
325
  }
326
+
327
+ async splashScreen() {
328
+ if (document.getElementById('made-with-q5play')) return;
329
+ if (!using_p5v2) $._incrementPreload();
330
+ let d = document.createElement('div');
331
+ d.id = 'made-with-q5play';
332
+ d.style =
333
+ 'position: absolute; width: 100%; height: 100%; top: 0; left: 0; z-index: 1000; background-color: black;';
334
+ let logo = document.createElement('img');
335
+ logo.style = `position: absolute; top: 50%; left: 50%; width: 80vmin; height: 40vmin; margin-left: -40vmin; margin-top: -20vmin; z-index: 1001; opacity: 1; scale: 1; transition: scale 1.5s, opacity 0.4s ease-in-out;`;
336
+ logo.onerror = () => {
337
+ logo.style.imageRendering = 'pixelated';
338
+ logo.src = `data:image/png;base64,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`;
339
+ };
340
+ let src = window._q5play_intro_image;
341
+ if (src == '' || src?.includes('made_with_q5play')) {
342
+ if (src.includes('bit.') || src.includes('pixel')) {
343
+ logo.style.imageRendering = 'pixelated';
344
+ }
345
+ logo.src = src;
346
+ } else {
347
+ logo.src = 'https://q5play.org/assets/made_with_q5play.webp';
348
+ }
349
+ await new Promise((r) => (logo.onload = r));
350
+ d.append(logo);
351
+ document.body.append(d);
352
+ await $.delay();
353
+ logo.offsetHeight; // trigger css reflow
354
+ logo.style.scale = 1.2;
355
+ await $.delay(1100);
356
+ logo.style.opacity = 0;
357
+ await $.delay(400);
358
+ d.style.display = 'none';
359
+ d.remove();
360
+ document.getElementById('made-with-q5play')?.remove();
361
+ if (!using_p5v2) $._decrementPreload();
362
+ }
327
363
  };
328
364
 
329
365
  $.q5play = new $.Q5Play();
@@ -334,7 +370,8 @@ async function q5playPreSetup(q) {
334
370
 
335
371
  // in q5play the default angle mode is degrees
336
372
  const DEGREES = $.DEGREES,
337
- DEGTORAD = Math.PI / 180;
373
+ DEGTORAD = Math.PI / 180,
374
+ RADTODEG = 180 / Math.PI;
338
375
  $.angleMode(DEGREES);
339
376
 
340
377
  // in q5play the default color mode is float RGB
@@ -1323,7 +1360,7 @@ async function q5playPreSetup(q) {
1323
1360
  if (typeof a3 == 'string') {
1324
1361
  rr = a4 || 0;
1325
1362
  path = getRegularPolygon(a2 - rr, a3);
1326
- } else if (Array.isArray(a2)) {
1363
+ } else if (typeof a2 == 'object') {
1327
1364
  path = a2;
1328
1365
  rr = a3;
1329
1366
  } else {
@@ -1334,6 +1371,12 @@ async function q5playPreSetup(q) {
1334
1371
  rr ??= 0;
1335
1372
 
1336
1373
  if (path) {
1374
+ if (!Array.isArray(path)) {
1375
+ let tmp = path.absoluteAngles;
1376
+ path = path.array;
1377
+ path.absoluteAngles = tmp;
1378
+ }
1379
+
1337
1380
  let start,
1338
1381
  vecs = [{ x: 0, y: 0 }],
1339
1382
  isLoop = (vecs.isLoop = false);
@@ -4741,6 +4784,27 @@ async function q5playPreSetup(q) {
4741
4784
  $.Visuals.prototype.addAni = $.Group.prototype.addAni = $.Sprite.prototype.addAni;
4742
4785
  $.Visuals.prototype.addAnis = $.Group.prototype.addAnis = $.Sprite.prototype.addAnis;
4743
4786
 
4787
+ class RayInfo {
4788
+ constructor(sprite, px, py, nx, ny, fraction, maxDistance) {
4789
+ this.sprite = sprite;
4790
+ this._px = px;
4791
+ this._py = py;
4792
+ this._nx = nx;
4793
+ this._ny = ny;
4794
+ this.distance = fraction * maxDistance;
4795
+ }
4796
+
4797
+ get intersect() {
4798
+ if (!this._intersect) this._intersect = scaleFrom(this._px, this._py);
4799
+ return this._intersect;
4800
+ }
4801
+
4802
+ get incidence() {
4803
+ if (this._incidence === undefined) this._incidence = $.atan2(this._ny, this._nx);
4804
+ return this._incidence;
4805
+ }
4806
+ }
4807
+
4744
4808
  $.World = class {
4745
4809
  constructor() {
4746
4810
  this.mod = {};
@@ -5097,57 +5161,56 @@ async function q5playPreSetup(q) {
5097
5161
  }
5098
5162
 
5099
5163
  rayCastAll(startPos, direction, maxDistance, limiter) {
5100
- // TODO
5101
- return [];
5102
- let start = scaleTo(startPos.x, startPos.y);
5103
-
5104
- let end;
5105
- if (typeof arguments[1] == 'number') {
5106
- end = scaleTo(startPos.x + maxDistance * $.cos(direction), startPos.y + maxDistance * $.sin(direction));
5164
+ const startX = startPos.x ?? startPos[0];
5165
+ const startY = startPos.y ?? startPos[1];
5166
+
5167
+ let endX, endY;
5168
+ if (typeof direction == 'number') {
5169
+ maxDistance ??= 10000;
5170
+ endX = startX + maxDistance * $.cos(direction);
5171
+ endY = startY + maxDistance * $.sin(direction);
5107
5172
  } else {
5108
- let endPos = arguments[1];
5109
- limiter ??= arguments[2];
5110
- end = scaleTo(endPos.x, endPos.y);
5173
+ const endPos = direction;
5174
+ limiter = maxDistance;
5175
+ endX = endPos.x ?? endPos[0];
5176
+ endY = endPos.y ?? endPos[1];
5111
5177
  }
5112
5178
 
5113
- let results = [];
5114
- let maxFraction = 1;
5115
-
5116
- super.rayCast(start, end, function (fixture, point, normal, fraction) {
5117
- let sprite = fixture.getBody().sprite;
5179
+ const origin = scaleTo(startX, startY);
5180
+ const translation = scaleTo(endX - startX, endY - startY);
5118
5181
 
5119
- let shouldLimit = limiter && limiter(sprite);
5120
-
5121
- // TODO provide advanced info: point and angle of intersection
5122
- results.push({
5123
- sprite,
5124
- // point,
5125
- // normal,
5126
- fraction
5127
- });
5182
+ const filter = new b2QueryFilter();
5183
+ filter.categoryBits = 0xffffffff;
5184
+ filter.maskBits = 0xffffffff;
5128
5185
 
5129
- // limit the ray cast so it can't go beyond this sprite
5130
- if (shouldLimit) {
5131
- if (fraction < maxFraction) {
5132
- maxFraction = fraction;
5133
- }
5134
- return fraction;
5186
+ const results = [];
5187
+
5188
+ b2World_CastRay(wID, origin, translation, filter, (castResult) => {
5189
+ const shape = shapeDict[castResult.shapeId.index1];
5190
+ if (shape?.sprite) {
5191
+ const s = shape.sprite;
5192
+ s.ray = new RayInfo(
5193
+ s,
5194
+ castResult.point.x,
5195
+ castResult.point.y,
5196
+ castResult.normal.x,
5197
+ castResult.normal.y,
5198
+ castResult.fraction,
5199
+ maxDistance
5200
+ );
5201
+ results.push(s);
5135
5202
  }
5136
- return 1; // keep casting the full length of the ray
5203
+ return 1; // continue to collect all hits
5137
5204
  });
5138
5205
 
5139
- // sort results by the distance from the starting position
5140
- results.sort((a, b) => a.fraction - b.fraction);
5206
+ filter.delete();
5141
5207
 
5142
- let sprites = [];
5208
+ // sort results by distance from start
5209
+ results.sort((a, b) => a.ray.distance - b.ray.distance);
5143
5210
 
5144
- for (let res of results) {
5145
- if (res.fraction <= maxFraction) {
5146
- sprites.push(res.sprite);
5147
- }
5148
- }
5211
+ if (!limiter) return results;
5149
5212
 
5150
- return sprites;
5213
+ return results.filter(limiter);
5151
5214
  }
5152
5215
  };
5153
5216
 
@@ -5332,7 +5395,7 @@ async function q5playPreSetup(q) {
5332
5395
  this.isActive = cameraOn = false;
5333
5396
  }
5334
5397
  }
5335
- }; //end camera class
5398
+ }; // end camera class
5336
5399
 
5337
5400
  $.Joint = class {
5338
5401
  constructor(spriteA, spriteB, type) {
@@ -5348,6 +5411,7 @@ async function q5playPreSetup(q) {
5348
5411
  this.type = type ??= 'glue';
5349
5412
  this.visible = true;
5350
5413
  this.deleted = false;
5414
+ this._springiness = 0;
5351
5415
 
5352
5416
  if (!a._shapes.length) a.addDefaultSensors();
5353
5417
  if (!b._shapes.length) b.addDefaultSensors();
@@ -5356,8 +5420,6 @@ async function q5playPreSetup(q) {
5356
5420
 
5357
5421
  let _this = this;
5358
5422
 
5359
- // if (type != 'slider' && type != 'rope') {
5360
-
5361
5423
  if (type != 'wheel') {
5362
5424
  this._offsetA = {};
5363
5425
 
@@ -5536,6 +5598,7 @@ async function q5playPreSetup(q) {
5536
5598
 
5537
5599
  _springMap(val) {
5538
5600
  if (val > 0) {
5601
+ this._springiness = val;
5539
5602
  if (val < 0.1) {
5540
5603
  val = $.map(val, 0, 0.1, 30, 4);
5541
5604
  } else if (val < 0.5) {
@@ -5572,7 +5635,7 @@ async function q5playPreSetup(q) {
5572
5635
  }
5573
5636
 
5574
5637
  get springiness() {
5575
- return Box2D.b2WeldJoint_GetLinearHertz(this.jID);
5638
+ return this._springiness;
5576
5639
  }
5577
5640
  set springiness(val) {
5578
5641
  val = this._springMap(val);
@@ -5648,7 +5711,7 @@ async function q5playPreSetup(q) {
5648
5711
  }
5649
5712
 
5650
5713
  get springiness() {
5651
- return Box2D.b2DistanceJoint_GetSpringHertz(this.jID);
5714
+ return this._springiness;
5652
5715
  }
5653
5716
  set springiness(val) {
5654
5717
  val = this._springMap(val);
@@ -5692,15 +5755,20 @@ async function q5playPreSetup(q) {
5692
5755
  constructor(spriteA, spriteB) {
5693
5756
  super(spriteA, spriteB, 'wheel');
5694
5757
 
5758
+ this._angle = $._angleMode == DEGREES ? 90 : Math.PI / 2;
5759
+ this._motorEnabled = false;
5760
+
5695
5761
  let j = this._init(b2DefaultWheelJointDef(wID));
5696
5762
  j.enableSpring = true;
5697
5763
  j.hertz = 4;
5698
5764
  j.dampingRatio = 0.7;
5699
- j.enableMotor = true;
5765
+ j.enableMotor = false; // neutral by default
5700
5766
  j.maxMotorTorque = 1000;
5701
5767
 
5702
5768
  this.jID = b2CreateWheelJoint(wID, j);
5703
5769
  jointDict[this.jID.index1] = this;
5770
+
5771
+ this._springiness = 0.1;
5704
5772
  }
5705
5773
 
5706
5774
  _init(j) {
@@ -5710,11 +5778,12 @@ async function q5playPreSetup(q) {
5710
5778
  j.base.bodyIdA = a.bdID;
5711
5779
  j.base.bodyIdB = b.bdID;
5712
5780
 
5713
- j.base.localFrameB.p = b2Body_GetLocalPoint(b.bdID, scaleTo(a.x, a.y));
5781
+ let rad = this._angle;
5782
+ if ($._angleMode == DEGREES) rad *= DEGTORAD;
5783
+ j.base.localFrameA.q.SetAngle(rad);
5714
5784
 
5715
- // let qA = b2Body_GetRotation(a.bdID);
5716
- // let qB = b2Body_GetRotation(b.bdID);
5717
- // j.base.localFrameA.q = b2InvMulRot(qA, qB);
5785
+ let pivot = scaleTo(b.x, b.y);
5786
+ j.base.localFrameA.p = b2Body_GetLocalPoint(a.bdID, pivot);
5718
5787
 
5719
5788
  return j;
5720
5789
  }
@@ -5727,9 +5796,12 @@ async function q5playPreSetup(q) {
5727
5796
  set angle(val) {
5728
5797
  if (val == this._angle) return;
5729
5798
  this._angle = val;
5730
- this._j.m_localXAxisA = new b2Vec2($.cos(val), $.sin(val));
5731
- this._j.m_localXAxisA.normalize();
5732
- this._j.m_localYAxisA = b2Vec2.crossNumVec2(1.0, this._j.m_localXAxisA);
5799
+ let rad = $._angleMode == DEGREES ? val * DEGTORAD : val;
5800
+
5801
+ let t = b2Joint_GetLocalFrameA(this.jID);
5802
+ t.q.SetAngle(rad);
5803
+ b2Joint_SetLocalFrameA(this.jID, t);
5804
+ b2Joint_WakeBodies(this.jID);
5733
5805
  }
5734
5806
 
5735
5807
  get springEnabled() {
@@ -5740,7 +5812,7 @@ async function q5playPreSetup(q) {
5740
5812
  }
5741
5813
 
5742
5814
  get springiness() {
5743
- return Box2D.b2WheelJoint_GetSpringHertz(this.jID);
5815
+ return this._springiness;
5744
5816
  }
5745
5817
  set springiness(val) {
5746
5818
  val = this._springMap(val);
@@ -5762,15 +5834,24 @@ async function q5playPreSetup(q) {
5762
5834
  }
5763
5835
 
5764
5836
  get lowerLimit() {
5765
- return Box2D.b2WheelJoint_GetLowerLimit(this.jID);
5837
+ return Box2D.b2WheelJoint_GetLowerLimit(this.jID) * meterSize;
5766
5838
  }
5767
5839
 
5768
5840
  get upperLimit() {
5769
- return Box2D.b2WheelJoint_GetUpperLimit(this.jID);
5841
+ return Box2D.b2WheelJoint_GetUpperLimit(this.jID) * meterSize;
5770
5842
  }
5771
5843
 
5772
- set limits(val) {
5773
- Box2D.b2WheelJoint_SetLimits(this.jID, val[0], val[1]);
5844
+ set range(val) {
5845
+ let min, max;
5846
+ if (typeof val == 'number') {
5847
+ val /= 2;
5848
+ min = -val;
5849
+ max = val;
5850
+ } else {
5851
+ min = val[0];
5852
+ max = val[1];
5853
+ }
5854
+ Box2D.b2WheelJoint_SetLimits(this.jID, min / meterSize, max / meterSize);
5774
5855
  }
5775
5856
 
5776
5857
  get motorEnabled() {
@@ -5778,13 +5859,16 @@ async function q5playPreSetup(q) {
5778
5859
  }
5779
5860
  set motorEnabled(val) {
5780
5861
  Box2D.b2WheelJoint_EnableMotor(this.jID, val);
5862
+ this._motorEnabled = val;
5781
5863
  }
5782
5864
 
5783
5865
  get maxPower() {
5784
5866
  return Box2D.b2WheelJoint_GetMaxMotorTorque(this.jID);
5785
5867
  }
5786
5868
  set maxPower(val) {
5869
+ if (val > 0 && !this._motorEnabled) this.motorEnabled = true;
5787
5870
  Box2D.b2WheelJoint_SetMaxMotorTorque(this.jID, val);
5871
+ b2Joint_WakeBodies(this.jID);
5788
5872
  }
5789
5873
 
5790
5874
  get power() {
@@ -5792,10 +5876,14 @@ async function q5playPreSetup(q) {
5792
5876
  }
5793
5877
 
5794
5878
  get speed() {
5879
+ // if in neutral, return the wheel's angular velocity
5880
+ if (!this._motorEnabled) return this.spriteB.rotationSpeed;
5795
5881
  return Box2D.b2WheelJoint_GetMotorSpeed(this.jID);
5796
5882
  }
5797
5883
  set speed(val) {
5884
+ if (!this._motorEnabled) this.motorEnabled = true;
5798
5885
  Box2D.b2WheelJoint_SetMotorSpeed(this.jID, val);
5886
+ b2Joint_WakeBodies(this.jID);
5799
5887
  }
5800
5888
  };
5801
5889
 
@@ -5823,7 +5911,7 @@ async function q5playPreSetup(q) {
5823
5911
  }
5824
5912
 
5825
5913
  get springiness() {
5826
- return Box2D.b2RevoluteJoint_GetSpringHertz(this.jID);
5914
+ return this._springiness;
5827
5915
  }
5828
5916
  set springiness(val) {
5829
5917
  val = this._springMap(val);
@@ -5863,8 +5951,8 @@ async function q5playPreSetup(q) {
5863
5951
  max = val[1];
5864
5952
  }
5865
5953
  if ($._angleMode == DEGREES) {
5866
- min *= $._RADTODEG;
5867
- max *= $._RADTODEG;
5954
+ min *= DEGTORAD;
5955
+ max *= DEGTORAD;
5868
5956
  }
5869
5957
  Box2D.b2RevoluteJoint_SetLimits(this.jID, min, max);
5870
5958
  }
@@ -5893,8 +5981,6 @@ async function q5playPreSetup(q) {
5893
5981
  set maxPower(val) {
5894
5982
  Box2D.b2RevoluteJoint_SetMaxMotorTorque(this.jID, val);
5895
5983
  }
5896
-
5897
- _display() {}
5898
5984
  };
5899
5985
 
5900
5986
  $.SliderJoint = class extends $.Joint {
@@ -5902,17 +5988,39 @@ async function q5playPreSetup(q) {
5902
5988
  super(spriteA, spriteB, 'slider');
5903
5989
 
5904
5990
  let j = this._init(b2DefaultPrismaticJointDef());
5905
- j.enableLimit = true;
5906
- j.lowerTranslation = -1;
5907
- j.upperTranslation = 1;
5991
+ j.enableLimit = false;
5908
5992
  j.enableMotor = true;
5909
- j.maxMotorForce = 50;
5993
+ j.maxMotorForce = 10;
5910
5994
  j.motorSpeed = 0;
5911
5995
 
5912
5996
  this.jID = b2CreatePrismaticJoint(wID, j);
5913
5997
  jointDict[this.jID.index1] = this;
5914
5998
  }
5915
5999
 
6000
+ _init(j) {
6001
+ let a = this.spriteA,
6002
+ b = this.spriteB;
6003
+
6004
+ j.base.bodyIdA = a.bdID;
6005
+ j.base.bodyIdB = b.bdID;
6006
+
6007
+ // set the anchor at sprite A's position in B's local frame
6008
+ j.base.localFrameB.p = b2Body_GetLocalPoint(b.bdID, scaleTo(a.x, a.y));
6009
+
6010
+ // align the slide axis with the vector from A to B
6011
+ let dx = b.x - a.x;
6012
+ let dy = b.y - a.y;
6013
+ let len = Math.sqrt(dx * dx + dy * dy);
6014
+ let angle = len > 0 ? Math.atan2(dy, dx) : 0;
6015
+
6016
+ let qA = b2Body_GetRotation(a.bdID);
6017
+ let qB = b2Body_GetRotation(b.bdID);
6018
+ j.base.localFrameA.q.SetAngle(angle - qA.GetAngle());
6019
+ j.base.localFrameB.q.SetAngle(angle - qB.GetAngle());
6020
+
6021
+ return j;
6022
+ }
6023
+
5916
6024
  get translation() {
5917
6025
  return Box2D.b2PrismaticJoint_GetTranslation(this.jID) * meterSize;
5918
6026
  }
@@ -5943,6 +6051,21 @@ async function q5playPreSetup(q) {
5943
6051
  max = val[1];
5944
6052
  }
5945
6053
  Box2D.b2PrismaticJoint_SetLimits(this.jID, min / meterSize, max / meterSize);
6054
+ Box2D.b2PrismaticJoint_EnableLimit(this.jID, true);
6055
+ }
6056
+
6057
+ set limits(val) {
6058
+ let min, max;
6059
+ if (typeof val == 'number') {
6060
+ val /= 2;
6061
+ min = -val;
6062
+ max = val;
6063
+ } else {
6064
+ min = val[0];
6065
+ max = val[1];
6066
+ }
6067
+ Box2D.b2PrismaticJoint_SetLimits(this.jID, min / meterSize, max / meterSize);
6068
+ Box2D.b2PrismaticJoint_EnableLimit(this.jID, true);
5946
6069
  }
5947
6070
 
5948
6071
  get springEnabled() {
@@ -5953,10 +6076,10 @@ async function q5playPreSetup(q) {
5953
6076
  }
5954
6077
 
5955
6078
  get springiness() {
5956
- return Box2D.b2PrismaticJoint_GetSpringHertz(this.jID);
6079
+ return this._springiness;
5957
6080
  }
5958
6081
  set springiness(val) {
5959
- val = this._springMap ? this._springMap(val) : val;
6082
+ val = this._springMap(val);
5960
6083
  Box2D.b2PrismaticJoint_SetSpringHertz(this.jID, val);
5961
6084
  }
5962
6085
 
@@ -5979,6 +6102,7 @@ async function q5playPreSetup(q) {
5979
6102
  }
5980
6103
  set speed(val) {
5981
6104
  Box2D.b2PrismaticJoint_SetMotorSpeed(this.jID, val);
6105
+ b2Joint_WakeBodies(this.jID);
5982
6106
  }
5983
6107
 
5984
6108
  get maxPower() {
@@ -5992,14 +6116,14 @@ async function q5playPreSetup(q) {
5992
6116
  return Box2D.b2PrismaticJoint_GetMotorForce(this.jID);
5993
6117
  }
5994
6118
 
5995
- // get energy() {
5996
- // return Box2D.b2PrismaticJoint_GetSpeed(this.jID);
5997
- // }
6119
+ get energy() {
6120
+ return Box2D.b2PrismaticJoint_GetSpeed(this.jID);
6121
+ }
5998
6122
  };
5999
6123
 
6000
6124
  $.GrabberJoint = class extends $.Joint {
6001
- constructor(pointer, sprite) {
6002
- sprite ??= pointer;
6125
+ constructor(grabPoint, sprite) {
6126
+ sprite ??= grabPoint;
6003
6127
  super(sprite, sprite, 'grabber');
6004
6128
 
6005
6129
  let bd = b2DefaultBodyDef();
@@ -6025,11 +6149,11 @@ async function q5playPreSetup(q) {
6025
6149
 
6026
6150
  this.jID = b2CreateMotorJoint(wID, j);
6027
6151
 
6028
- let offX = sprite.x - (pointer[0] || pointer.x),
6029
- offY = sprite.y - (pointer[1] || pointer.y);
6152
+ let offX = sprite.x - (grabPoint[0] || grabPoint.x),
6153
+ offY = sprite.y - (grabPoint[1] || grabPoint.y);
6030
6154
  if (!isSlop(offX) || !isSlop(offY)) {
6031
6155
  this._setOffsetB(-offX, -offY);
6032
- this.target = pointer;
6156
+ this.target = grabPoint;
6033
6157
  }
6034
6158
 
6035
6159
  this.sprite = sprite;
@@ -6352,23 +6476,23 @@ async function q5playPreSetup(q) {
6352
6476
  if (!ani.looping) return;
6353
6477
  }
6354
6478
 
6355
- //going to target frame up
6479
+ // going to target frame up
6356
6480
  if (ani.targetFrame > ani._frame && ani.targetFrame !== -1) {
6357
6481
  ani._frame++;
6358
6482
  }
6359
- //going to target frame down
6483
+ // going to target frame down
6360
6484
  else if (ani.targetFrame < ani._frame && ani.targetFrame !== -1) {
6361
6485
  ani._frame--;
6362
6486
  } else if (ani.targetFrame === ani._frame && ani.targetFrame !== -1) {
6363
6487
  ani.playing = false;
6364
6488
  } else if (ani.looping) {
6365
- //advance frame
6366
- //if next frame is too high
6489
+ // advance frame
6490
+ // if next frame is too high
6367
6491
  if (ani._frame >= ani.lastFrame) {
6368
6492
  ani._frame = 0;
6369
6493
  } else ani._frame++;
6370
6494
  } else {
6371
- //if next frame is too high
6495
+ // if next frame is too high
6372
6496
  if (ani._frame < ani.lastFrame) ani._frame++;
6373
6497
  }
6374
6498
  } else {
@@ -6384,42 +6508,6 @@ async function q5playPreSetup(q) {
6384
6508
  });
6385
6509
  };
6386
6510
 
6387
- async function playIntro() {
6388
- if (document.getElementById('made-with-q5play')) return;
6389
- if (!using_p5v2) $._incrementPreload();
6390
- let d = document.createElement('div');
6391
- d.id = 'made-with-q5play';
6392
- d.style = 'position: absolute; width: 100%; height: 100%; top: 0; left: 0; z-index: 1000; background-color: black;';
6393
- let logo = document.createElement('img');
6394
- logo.style = `position: absolute; top: 50%; left: 50%; width: 80vmin; height: 40vmin; margin-left: -40vmin; margin-top: -20vmin; z-index: 1001; opacity: 1; scale: 1; transition: scale 1.5s, opacity 0.4s ease-in-out;`;
6395
- logo.onerror = () => {
6396
- logo.style.imageRendering = 'pixelated';
6397
- logo.src = `data:image/png;base64,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`;
6398
- };
6399
- let src = window._q5play_intro_image;
6400
- if (src == '' || src?.includes('made_with_q5play')) {
6401
- if (src.includes('bit.') || src.includes('pixel')) {
6402
- logo.style.imageRendering = 'pixelated';
6403
- }
6404
- logo.src = src;
6405
- } else {
6406
- logo.src = 'https://q5play.org/assets/made_with_q5play.webp';
6407
- }
6408
- await new Promise((r) => (logo.onload = r));
6409
- d.append(logo);
6410
- document.body.append(d);
6411
- await $.delay();
6412
- logo.offsetHeight; // trigger css reflow
6413
- logo.style.scale = 1.2;
6414
- await $.delay(1100);
6415
- logo.style.opacity = 0;
6416
- await $.delay(400);
6417
- d.style.display = 'none';
6418
- d.remove();
6419
- document.getElementById('made-with-q5play')?.remove();
6420
- if (!using_p5v2) $._decrementPreload();
6421
- }
6422
-
6423
6511
  if (window.location) {
6424
6512
  let lh = location.hostname;
6425
6513
  switch (lh) {
@@ -6452,7 +6540,7 @@ async function q5playPreSetup(q) {
6452
6540
  ) {
6453
6541
  break;
6454
6542
  }
6455
- playIntro();
6543
+ $.q5play.splashScreen();
6456
6544
  }
6457
6545
  }
6458
6546
 
@@ -7767,10 +7855,6 @@ async function q5playPreSetup(q) {
7767
7855
 
7768
7856
  s = $.q5play.sprites[uid];
7769
7857
 
7770
- if (s && !s.visible) {
7771
- continue;
7772
- }
7773
-
7774
7858
  let type = Box2D.HEAPU8[offset];
7775
7859
 
7776
7860
  if (type == 7) {
@@ -7799,8 +7883,12 @@ async function q5playPreSetup(q) {
7799
7883
  s._velSynced = false;
7800
7884
  s._vel._magCached = false;
7801
7885
 
7886
+ if (!s.visible) {
7887
+ continue;
7888
+ }
7889
+
7802
7890
  if (s._hasImagery || s._userDefinedDraw) {
7803
- s._rotation = Math.atan2(data[2], data[3]) * $._RADTODEG;
7891
+ s._rotation = Math.atan2(data[2], data[3]) * RADTODEG;
7804
7892
  }
7805
7893
 
7806
7894
  if (s.debug || (!s._hasImagery && !s._userDefinedDraw)) {