q5play 4.1.2 → 4.2.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 (5) hide show
  1. package/package.json +1 -1
  2. package/py.typed +0 -0
  3. package/q5play.d.ts +1369 -144
  4. package/q5play.js +466 -125
  5. package/q5play.pyi +5007 -0
package/q5play.js CHANGED
@@ -12,12 +12,12 @@
12
12
  * |__/ |__/ \______/
13
13
  *
14
14
  * @package q5play
15
- * @version 4.1
15
+ * @version 4.2
16
16
  * @author quinton-ashley
17
17
  * @website https://q5play.org
18
18
  */
19
19
 
20
- let q5play_version = '4.1';
20
+ let q5play_version = '4.2';
21
21
 
22
22
  if (typeof globalThis.Q5 == 'undefined') {
23
23
  console.error('q5play requires q5.js to be loaded first. Visit https://q5js.org to learn more.');
@@ -396,7 +396,9 @@ async function q5playPreSetup(q) {
396
396
  const scaleFrom = (x, y) => ({ x: x * meterSize, y: y * meterSize });
397
397
 
398
398
  const linearSlop = 0.005,
399
- angularSlop = 0.000582;
399
+ angularSlop = 0.0333;
400
+
401
+ let visualSlop = 0.5;
400
402
 
401
403
  const isSlop = (val) => Math.abs(val) <= linearSlop;
402
404
  const fixRound = (val) => {
@@ -1199,10 +1201,10 @@ async function q5playPreSetup(q) {
1199
1201
  else this.addCollider(...args);
1200
1202
  }
1201
1203
 
1202
- this.prevPos = { x, y };
1204
+ this.prevX = x;
1205
+ this.prevY = y;
1203
1206
  this.prevRotation = 0;
1204
- this._dest = { x, y };
1205
- this._destIdx = 0;
1207
+
1206
1208
  this._debug = false;
1207
1209
 
1208
1210
  if (!group._isAllSpritesGroup) $.allSprites.push(this);
@@ -1891,7 +1893,7 @@ async function q5playPreSetup(q) {
1891
1893
  }
1892
1894
  const speed = this._vel.mag();
1893
1895
  if (speed) {
1894
- this._setVel($.cos(val) * speed, $.sin(val) * speed);
1896
+ this.__setVel($.cos(val) * speed, $.sin(val) * speed);
1895
1897
  this._vel._magCached = false;
1896
1898
  }
1897
1899
  this._vel._direction = val;
@@ -1999,19 +2001,17 @@ async function q5playPreSetup(q) {
1999
2001
  this._opacity = val;
2000
2002
  }
2001
2003
 
2002
- get previousPosition() {
2003
- return this.prevPos;
2004
+ get previousX() {
2005
+ return this.prevX;
2004
2006
  }
2005
- set previousPosition(val) {
2006
- this.prevPos = val;
2007
+
2008
+ get previousY() {
2009
+ return this.prevY;
2007
2010
  }
2008
2011
 
2009
2012
  get previousRotation() {
2010
2013
  return this.prevRotation;
2011
2014
  }
2012
- set previousRotation(val) {
2013
- this.prevRotation = val;
2014
- }
2015
2015
 
2016
2016
  get pixelPerfect() {
2017
2017
  return this._pixelPerfect;
@@ -2035,8 +2035,9 @@ async function q5playPreSetup(q) {
2035
2035
  get rotation() {
2036
2036
  if (!this._physicsEnabled || !usePhysics) return this._rotation || 0;
2037
2037
  let val = b2Body_GetRotation(this.bdID).GetAngle();
2038
+ if ($._angleMode == DEGREES) val = $.degrees(val);
2038
2039
  if (friendlyRounding) val = fixRoundAngular(val);
2039
- return (this._rotation = $._angleMode == DEGREES ? $.degrees(val) : val);
2040
+ return (this._rotation = val);
2040
2041
  }
2041
2042
  set rotation(val) {
2042
2043
  this._rotation = val;
@@ -2153,14 +2154,14 @@ async function q5playPreSetup(q) {
2153
2154
  return this._vel.mag();
2154
2155
  }
2155
2156
  set speed(val) {
2156
- if (!val) this._setVel(0, 0);
2157
+ if (!val) this.__setVel(0, 0);
2157
2158
  else {
2158
2159
  const mag = this._vel.mag();
2159
2160
  if (mag > 0) {
2160
- this._setVel((this._vel.x / mag) * val, (this._vel.y / mag) * val);
2161
+ this.__setVel((this._vel.x / mag) * val, (this._vel.y / mag) * val);
2161
2162
  } else {
2162
2163
  const dir = this._vel.direction();
2163
- this._setVel($.cos(dir) * val, $.sin(dir) * val);
2164
+ this.__setVel($.cos(dir) * val, $.sin(dir) * val);
2164
2165
  }
2165
2166
  }
2166
2167
  this._vel._mag = val;
@@ -2168,7 +2169,7 @@ async function q5playPreSetup(q) {
2168
2169
  }
2169
2170
 
2170
2171
  setSpeedAndDirection(speed, direction) {
2171
- this._setVel($.cos(direction) * speed, $.sin(direction) * speed);
2172
+ this.__setVel($.cos(direction) * speed, $.sin(direction) * speed);
2172
2173
  this._vel._mag = speed;
2173
2174
  this._vel._direction = direction;
2174
2175
  this._vel._magCached = this._vel._directionCached = true;
@@ -2230,7 +2231,7 @@ async function q5playPreSetup(q) {
2230
2231
  }
2231
2232
 
2232
2233
  get pos() {
2233
- return this._pos;
2234
+ return { x: this._posX, y: this._posY };
2234
2235
  }
2235
2236
  set pos(val) {
2236
2237
  if (val == $.mouse && !$.mouse.isActive) return;
@@ -2409,7 +2410,6 @@ async function q5playPreSetup(q) {
2409
2410
  }
2410
2411
  set vel(val) {
2411
2412
  this._setVel(val[0] ?? val.x, val[1] ?? val.y);
2412
- this._vel._magCached = this._vel._directionCached = false;
2413
2413
  }
2414
2414
 
2415
2415
  _setVel(x, y) {
@@ -2419,6 +2419,16 @@ async function q5playPreSetup(q) {
2419
2419
  this._velX = x;
2420
2420
  this._velY = y;
2421
2421
  this._velSynced = true;
2422
+ this._vel._magCached = this._vel._directionCached = false;
2423
+ }
2424
+
2425
+ __setVel(x, y) {
2426
+ if (this._physicsEnabled) {
2427
+ b2Body_SetLinearVelocity(this.bdID, new b2Vec2(x, y));
2428
+ }
2429
+ this._velX = x;
2430
+ this._velY = y;
2431
+ this._velSynced = true;
2422
2432
  }
2423
2433
 
2424
2434
  get velocity() {
@@ -2447,6 +2457,9 @@ async function q5playPreSetup(q) {
2447
2457
 
2448
2458
  _update() {
2449
2459
  if (this._customUpdate) this._customUpdate();
2460
+ this.prevX = this._posX;
2461
+ this.prevY = this._posY;
2462
+ this.prevRotation = this.rotation;
2450
2463
  if (this.autoUpdate) this.autoUpdate = null;
2451
2464
  }
2452
2465
 
@@ -2468,6 +2481,70 @@ async function q5playPreSetup(q) {
2468
2481
  }
2469
2482
  }
2470
2483
 
2484
+ if (this._destArrivalTime !== undefined && $.world.physicsTime >= this._destArrivalTime) {
2485
+ const x = this._posX,
2486
+ y = this._posY,
2487
+ prevX = this.prevX,
2488
+ prevY = this.prevY,
2489
+ destX = this._destX,
2490
+ destY = this._destY;
2491
+
2492
+ const destReachedX =
2493
+ destX === undefined || (destX >= Math.min(prevX, x) - visualSlop && destX <= Math.max(prevX, x) + visualSlop);
2494
+ const destReachedY =
2495
+ destY === undefined || (destY >= Math.min(prevY, y) - visualSlop && destY <= Math.max(prevY, y) + visualSlop);
2496
+ const destReached = destReachedX && destReachedY;
2497
+
2498
+ if (destReached) {
2499
+ this._setVel(0, 0);
2500
+ this.rotationSpeed = 0;
2501
+ this.pos = [this._destX ?? this._posX, this._destY ?? this._posY];
2502
+ if (this._destRot !== undefined) {
2503
+ this.rotation = this._destRot;
2504
+ }
2505
+ }
2506
+
2507
+ if (this._destResolve) {
2508
+ this._destResolve(destReached);
2509
+ this._destResolve = undefined;
2510
+ }
2511
+
2512
+ this._destX = this._destY = this._destRot = this._destArrivalTime = undefined;
2513
+ }
2514
+
2515
+ if (this._destRotArrivalTime !== undefined && $.world.physicsTime >= this._destRotArrivalTime) {
2516
+ const isDeg = $._angleMode == DEGREES,
2517
+ full = isDeg ? 360 : $.TWO_PI,
2518
+ half = isDeg ? 180 : Math.PI,
2519
+ prevRot = this.prevRotation,
2520
+ currRot = this.rotation,
2521
+ destRot = this._destRot;
2522
+
2523
+ // normalize destRot to [-half, half) to match this.rotation's range
2524
+ let nd = ((destRot % full) + full) % full;
2525
+ if (nd >= half) nd -= full;
2526
+
2527
+ // when the sprite crosses the ±half boundary, prevRot and currRot jump
2528
+ // by ~full°; invert the range check so nd is tested against the arc that
2529
+ // was actually traversed rather than the gap between them
2530
+ const crossed180 = Math.abs(prevRot - currRot) > half;
2531
+ const rotReached = crossed180
2532
+ ? nd >= Math.max(prevRot, currRot) - visualSlop || nd <= Math.min(prevRot, currRot) + visualSlop
2533
+ : nd >= Math.min(prevRot, currRot) - visualSlop && nd <= Math.max(prevRot, currRot) + visualSlop;
2534
+
2535
+ if (rotReached) {
2536
+ this.rotationSpeed = 0;
2537
+ this.rotation = nd;
2538
+ }
2539
+
2540
+ if (this._destRotationResolve) {
2541
+ this._destRotationResolve(rotReached);
2542
+ this._destRotationResolve = undefined;
2543
+ }
2544
+
2545
+ this._destRot = this._destRotArrivalTime = undefined;
2546
+ }
2547
+
2471
2548
  if (!this._physicsEnabled && !this._deleted) return;
2472
2549
 
2473
2550
  this.__step();
@@ -2776,32 +2853,6 @@ async function q5playPreSetup(q) {
2776
2853
  wind.delete();
2777
2854
  }
2778
2855
 
2779
- angleTo(x, y) {
2780
- if (typeof x == 'object') {
2781
- y = x.y;
2782
- x = x.x;
2783
- }
2784
- return $.atan2(y - this.y, x - this.x);
2785
- }
2786
-
2787
- rotationToFace(x, y, facing) {
2788
- if (typeof x == 'object') {
2789
- facing = y;
2790
- y = x.y;
2791
- x = x.x;
2792
- }
2793
- // if the sprite is too close to the position, don't rotate
2794
- if (Math.abs(x - this.x) < 0.01 && Math.abs(y - this.y) < 0.01) {
2795
- return 0;
2796
- }
2797
- return this.angleTo(x, y) + (facing || 0);
2798
- }
2799
-
2800
- angleToFace(x, y, facing) {
2801
- let ang = this.rotationToFace(x, y, facing);
2802
- return minAngleDist(ang, this.rotation);
2803
- }
2804
-
2805
2856
  moveTowards(x, y, tracking) {
2806
2857
  if (x === undefined) return;
2807
2858
 
@@ -2816,13 +2867,13 @@ async function q5playPreSetup(q) {
2816
2867
 
2817
2868
  let velX, velY;
2818
2869
 
2819
- if (x !== null) {
2870
+ if (x !== null && x !== undefined) {
2820
2871
  let diffX = x - this.x;
2821
2872
  if (!isSlop(diffX)) {
2822
2873
  velX = diffX * tracking;
2823
2874
  } else velX = 0;
2824
2875
  } else velX = this._velX;
2825
- if (y !== null) {
2876
+ if (y !== null && y !== undefined) {
2826
2877
  let diffY = y - this.y;
2827
2878
  if (!isSlop(diffY)) {
2828
2879
  velY = diffY * tracking;
@@ -2830,7 +2881,155 @@ async function q5playPreSetup(q) {
2830
2881
  } else velY = this._velY;
2831
2882
 
2832
2883
  this._setVel(velX, velY);
2833
- this._vel._magCached = this._vel._directionCached = false;
2884
+ }
2885
+
2886
+ moveTo(x, y, speed) {
2887
+ if (x === undefined && (y === undefined || y === null)) return;
2888
+
2889
+ if (x !== null && x !== undefined && typeof x != 'number') {
2890
+ let pos = x;
2891
+ if (pos == $.mouse && !$.mouse.isActive) return;
2892
+ speed = y;
2893
+ y = pos.y;
2894
+ x = pos.x;
2895
+ }
2896
+
2897
+ const moveX = x !== null && x !== undefined;
2898
+ const moveY = y !== null && y !== undefined;
2899
+
2900
+ speed ||= this.speed || 1;
2901
+
2902
+ const dist =
2903
+ moveX && moveY
2904
+ ? Math.hypot(x - this._posX, y - this._posY)
2905
+ : moveX
2906
+ ? Math.abs(x - this._posX)
2907
+ : Math.abs(y - this._posY);
2908
+
2909
+ if (this._destResolve) {
2910
+ this._destResolve(false);
2911
+ this._destResolve = undefined;
2912
+ }
2913
+
2914
+ this._destX = moveX ? x : undefined;
2915
+ this._destY = moveY ? y : undefined;
2916
+ this._destArrivalTime = $.world.physicsTime + dist / (speed * $.world._updateRate);
2917
+
2918
+ if (dist > 0) {
2919
+ if (moveX && moveY) {
2920
+ this.setSpeedAndDirection(speed, $.atan2(y - this.y, x - this.x));
2921
+ } else if (moveX) {
2922
+ this._setVel(x > this._posX ? speed : -speed, this._velY);
2923
+ } else {
2924
+ this._setVel(this._velX, y > this._posY ? speed : -speed);
2925
+ }
2926
+ }
2927
+
2928
+ return {
2929
+ then: (onFulfilled) => {
2930
+ this._destResolve = onFulfilled;
2931
+ }
2932
+ };
2933
+ }
2934
+
2935
+ angleTo(x, y, facing = 0) {
2936
+ if (typeof x == 'object') {
2937
+ facing = y || 0;
2938
+ y = x.y;
2939
+ x = x.x;
2940
+ }
2941
+ // if the sprite is too close to the position, don't rotate
2942
+ if (Math.abs(x - this.x) < 0.01 && Math.abs(y - this.y) < 0.01) {
2943
+ return this.rotation;
2944
+ }
2945
+ return $.atan2(y - this.y, x - this.x) + facing;
2946
+ }
2947
+
2948
+ angleDistTo(x, y, facing = 0) {
2949
+ if (typeof x == 'object') {
2950
+ facing = y || 0;
2951
+ y = x.y;
2952
+ x = x.x;
2953
+ }
2954
+ // if the sprite is too close to the position, don't rotate
2955
+ if (Math.abs(x - this.x) < 0.01 && Math.abs(y - this.y) < 0.01) {
2956
+ return 0;
2957
+ }
2958
+ return minAngleDist($.atan2(y - this.y, x - this.x) + facing, this.rotation);
2959
+ }
2960
+
2961
+ rotateTo(angle, speed) {
2962
+ let args = arguments;
2963
+ let x, y, facing;
2964
+ if (typeof args[0] != 'number') {
2965
+ x = args[0].x;
2966
+ y = args[0].y;
2967
+ speed = args[1];
2968
+ facing = args[2];
2969
+ } else if (arguments.length > 2) {
2970
+ x = args[0];
2971
+ y = args[1];
2972
+ speed = args[2];
2973
+ facing = args[3];
2974
+ }
2975
+
2976
+ if (x !== undefined) angle = this.angleTo(x, y, facing);
2977
+
2978
+ const full = $._angleMode == DEGREES ? 360 : $.TWO_PI;
2979
+ let angleDist = (angle - this.rotation) % full;
2980
+ if (angleDist < 0 && speed > 0) angleDist += full;
2981
+ if (angleDist > 0 && speed < 0) angleDist -= full;
2982
+
2983
+ return this.rotate(angleDist, speed);
2984
+ }
2985
+
2986
+ rotateMinTo(angle, speed, facing) {
2987
+ let args = arguments;
2988
+ let x, y;
2989
+ if (typeof args[0] != 'number') {
2990
+ x = args[0].x;
2991
+ y = args[0].y;
2992
+ speed = args[1];
2993
+ facing = args[2];
2994
+ } else if (args.length > 2) {
2995
+ x = args[0];
2996
+ y = args[1];
2997
+ speed = args[2];
2998
+ facing = args[3];
2999
+ }
3000
+
3001
+ if (x !== undefined) angle = this.angleTo(x, y, facing);
3002
+
3003
+ return this.rotate(minAngleDist(angle, this.rotation), speed);
3004
+ }
3005
+
3006
+ rotate(angleDist, speed) {
3007
+ if (Math.abs(angleDist) <= angularSlop) return;
3008
+
3009
+ speed ||= this.rotationSpeed || 1;
3010
+ speed = Math.abs(speed) * Math.sign(angleDist);
3011
+
3012
+ // cap speed so the sprite doesn't overshoot the destination in one physics step
3013
+ if (Math.abs(speed) > Math.abs(angleDist)) speed = angleDist;
3014
+
3015
+ this._destRotArrivalTime = $.world.physicsTime + Math.abs(angleDist) / (Math.abs(speed) * $.world._updateRate);
3016
+
3017
+ this._destRot = this.rotation + angleDist;
3018
+
3019
+ if (this._destRotationResolve) {
3020
+ this._destRotationResolve(false);
3021
+ this._destRotationResolve = undefined;
3022
+ }
3023
+
3024
+ this.rotationSpeed = speed;
3025
+
3026
+ log(this.rotation, this._destRot, angleDist, speed, this._destRotArrivalTime);
3027
+
3028
+ return {
3029
+ then: (onFulfilled) => {
3030
+ this._destRotationResolve = onFulfilled;
3031
+ }
3032
+ };
2834
3033
  }
2835
3034
 
2836
3035
  rotateTowards(angle, tracking) {
@@ -2848,18 +3047,31 @@ async function q5playPreSetup(q) {
2848
3047
  facing = args[3];
2849
3048
  }
2850
3049
 
2851
- if (x !== undefined) angle = this.angleToFace(x, y, facing);
3050
+ if (x !== undefined) angle = this.angleDistTo(x, y, facing);
2852
3051
  else angle -= this.rotation;
2853
3052
 
2854
3053
  tracking ??= 0.1;
2855
3054
  this.rotationSpeed = angle * tracking;
2856
3055
  }
2857
3056
 
2858
- _setTargetTransform(x, y, rotation) {
2859
- let t = new b2Transform();
2860
- t.p = scaleTo(x, y);
3057
+ transformTowards(x, y, rotation, tracking = 0.1) {
3058
+ if (x === undefined) return;
3059
+
3060
+ if (typeof x != 'number') {
3061
+ let pos = x;
3062
+ if (pos == $.mouse && !$.mouse.isActive) return;
3063
+ tracking = rotation ?? tracking;
3064
+ rotation = y;
3065
+ y = pos.y;
3066
+ x = pos.x;
3067
+ }
3068
+
3069
+ const t = new b2Transform();
3070
+ t.p = scaleTo(x ?? this._posX, y ?? this._posY);
3071
+ rotation ??= this._rotation;
3072
+ if ($._angleMode == DEGREES) rotation = (rotation % 360) * DEGTORAD;
2861
3073
  t.q = b2MakeRot(rotation);
2862
- b2Body_SetTargetTransform(this.bdID, t, $.world._timeStep);
3074
+ b2Body_SetTargetTransform(this.bdID, t, $.world._timeStep / tracking);
2863
3075
  }
2864
3076
 
2865
3077
  delete() {
@@ -4532,6 +4744,134 @@ async function q5playPreSetup(q) {
4532
4744
  }
4533
4745
  }
4534
4746
 
4747
+ rotateTowards() {
4748
+ for (let s of this) {
4749
+ s.rotateTowards(...arguments);
4750
+ }
4751
+ }
4752
+
4753
+ rotateTo(angle, speed) {
4754
+ const thenables = [];
4755
+ for (let s of this) {
4756
+ thenables.push(s.rotateTo(...arguments));
4757
+ }
4758
+ return {
4759
+ then: (onFulfilled) => {
4760
+ let pending = thenables.length;
4761
+ if (!pending) return onFulfilled(true);
4762
+ let allReached = true;
4763
+ for (let t of thenables) {
4764
+ t.then((reached) => {
4765
+ if (!reached) allReached = false;
4766
+ if (--pending === 0) onFulfilled(allReached);
4767
+ });
4768
+ }
4769
+ }
4770
+ };
4771
+ }
4772
+
4773
+ rotate(angle, speed) {
4774
+ const thenables = [];
4775
+ for (let s of this) {
4776
+ thenables.push(s.rotate(...arguments));
4777
+ }
4778
+ return {
4779
+ then: (onFulfilled) => {
4780
+ let pending = thenables.length;
4781
+ if (!pending) return onFulfilled(true);
4782
+ let allReached = true;
4783
+ for (let t of thenables) {
4784
+ t.then((reached) => {
4785
+ if (!reached) allReached = false;
4786
+ if (--pending === 0) onFulfilled(allReached);
4787
+ });
4788
+ }
4789
+ }
4790
+ };
4791
+ }
4792
+
4793
+ rotateMinTo(angle, speed) {
4794
+ const thenables = [];
4795
+ for (let s of this) {
4796
+ thenables.push(s.rotateMinTo(...arguments));
4797
+ }
4798
+ return {
4799
+ then: (onFulfilled) => {
4800
+ let pending = thenables.length;
4801
+ if (!pending) return onFulfilled(true);
4802
+ let allReached = true;
4803
+ for (let t of thenables) {
4804
+ t.then((reached) => {
4805
+ if (!reached) allReached = false;
4806
+ if (--pending === 0) onFulfilled(allReached);
4807
+ });
4808
+ }
4809
+ }
4810
+ };
4811
+ }
4812
+
4813
+ transformTowards(x, y, rotation, tracking = 0.1) {
4814
+ if (x === undefined) return;
4815
+
4816
+ if (typeof x != 'number') {
4817
+ let pos = x;
4818
+ if (pos == $.mouse && !$.mouse.isActive) return;
4819
+ tracking = rotation ?? tracking;
4820
+ rotation = y;
4821
+ y = pos.y;
4822
+ x = pos.x;
4823
+ }
4824
+
4825
+ this._resetCentroid();
4826
+
4827
+ for (let s of this) {
4828
+ if (s.distCentroid === undefined) {
4829
+ this._resetDistancesFromCentroid();
4830
+ }
4831
+ s.transformTowards(s.distCentroid.x + x, s.distCentroid.y + y, rotation, tracking);
4832
+ }
4833
+ }
4834
+
4835
+ moveTo(x, y, speed) {
4836
+ if (x === undefined && (y === undefined || y === null)) return;
4837
+
4838
+ let nullX = x === null || x === undefined;
4839
+ let nullY = y === null || y === undefined;
4840
+
4841
+ if (!nullX && typeof x != 'number') {
4842
+ let pos = x;
4843
+ if (pos == $.mouse && !$.mouse.isActive) return;
4844
+ speed = y;
4845
+ y = pos.y;
4846
+ x = pos.x;
4847
+ nullX = nullY = false;
4848
+ }
4849
+
4850
+ this._resetCentroid();
4851
+ this._resetDistancesFromCentroid();
4852
+
4853
+ const thenables = [];
4854
+ for (let s of this) {
4855
+ const tx = nullX ? null : s.distCentroid.x + x;
4856
+ const ty = nullY ? null : s.distCentroid.y + y;
4857
+ thenables.push(s.moveTo(tx, ty, speed));
4858
+ }
4859
+
4860
+ return {
4861
+ then: (onFulfilled) => {
4862
+ let pending = thenables.length;
4863
+ if (!pending) return onFulfilled(true);
4864
+ let allReached = true;
4865
+ for (let t of thenables) {
4866
+ t.then((reached) => {
4867
+ if (!reached) allReached = false;
4868
+ if (--pending === 0) onFulfilled(allReached);
4869
+ });
4870
+ }
4871
+ }
4872
+ };
4873
+ }
4874
+
4535
4875
  toString() {
4536
4876
  return 'g' + this.idNum;
4537
4877
  }
@@ -5040,6 +5380,7 @@ async function q5playPreSetup(q) {
5040
5380
  }
5041
5381
  set meterSize(val) {
5042
5382
  meterSize = val;
5383
+ visualSlop = meterSize / 120;
5043
5384
  }
5044
5385
 
5045
5386
  get profile() {
@@ -5155,6 +5496,8 @@ async function q5playPreSetup(q) {
5155
5496
 
5156
5497
  this.physicsTime += timeStep;
5157
5498
 
5499
+ this._sync();
5500
+
5158
5501
  let sprites = Object.values($.q5play.sprites);
5159
5502
  let groups = Object.values($.q5play.groups);
5160
5503
 
@@ -5171,6 +5514,71 @@ async function q5playPreSetup(q) {
5171
5514
  if (this.autoStep) this.autoStep = null;
5172
5515
  }
5173
5516
 
5517
+ _sync() {
5518
+ jointStack = [];
5519
+ shapeStack = [];
5520
+
5521
+ b2World_Draw(wID, drawCmds.GetDebugDraw());
5522
+
5523
+ let cmdPtr = drawCmds.GetCommandsData(),
5524
+ cmdSize = drawCmds.GetCommandsSize(),
5525
+ cmdStride = drawCmds.GetCommandStride(),
5526
+ offset = cmdPtr,
5527
+ renderJointForces = $.q5play.renderJointForces,
5528
+ s;
5529
+
5530
+ for (let i = 0; i < cmdSize; i++, offset += cmdStride) {
5531
+ // workaround that unpacks data from
5532
+ // the shape material's customColor
5533
+ const customColor = Box2D.HEAPU32[(offset + 4) >> 2],
5534
+ uid = customColor & 0xffffff,
5535
+ isSensor = (customColor >>> 25) & 0x1,
5536
+ isFirstShape = (customColor >>> 26) & 0x1;
5537
+
5538
+ s = $.q5play.sprites[uid];
5539
+
5540
+ let type = Box2D.HEAPU8[offset];
5541
+
5542
+ if (type == 7) {
5543
+ continue;
5544
+ }
5545
+
5546
+ let vertexCount = Box2D.HEAPU16[(offset + 8) >> 1];
5547
+
5548
+ let dataLen = 4;
5549
+ if (type == 1) dataLen = 5 + vertexCount * 2;
5550
+ else if (type == 3 || type == 4) dataLen = 5;
5551
+ let data = new Float32Array(Box2D.HEAPU8.buffer, offset + 12, dataLen);
5552
+
5553
+ if (!s) {
5554
+ if (type == 0 && renderJointForces) jointStack.push(data);
5555
+ continue;
5556
+ }
5557
+
5558
+ // always keep position in sync since it has a low performance cost
5559
+ // unless the shape is a chain
5560
+ if (type < 4 || (type == 4 && !s._hasCapsuleChain)) {
5561
+ s._posX = data[0] * meterSize;
5562
+ s._posY = data[1] * meterSize;
5563
+ }
5564
+
5565
+ s._velSynced = false;
5566
+ s._vel._magCached = false;
5567
+
5568
+ if (!s.visible) {
5569
+ continue;
5570
+ }
5571
+
5572
+ if (s._hasImagery || s._userDefinedDraw) {
5573
+ s._rotation = Math.atan2(data[2], data[3]) * RADTODEG;
5574
+ }
5575
+
5576
+ if (s.debug || (!s._hasImagery && !s._userDefinedDraw)) {
5577
+ shapeStack.push({ type, sprite: s, isSensor, isFirstShape, data, vertexCount });
5578
+ }
5579
+ }
5580
+ }
5581
+
5174
5582
  get realTime() {
5175
5583
  return $.millis() / 1000;
5176
5584
  }
@@ -5298,7 +5706,7 @@ async function q5playPreSetup(q) {
5298
5706
  }
5299
5707
 
5300
5708
  get pos() {
5301
- return this._pos;
5709
+ return { x: this._pos.x, y: this._pos.y };
5302
5710
  }
5303
5711
  set pos(val) {
5304
5712
  this.x = val[0] ?? val.x;
@@ -6966,7 +7374,7 @@ async function q5playPreSetup(q) {
6966
7374
  }
6967
7375
 
6968
7376
  get pos() {
6969
- return this._pos;
7377
+ return { x: this.x, y: this.y };
6970
7378
  }
6971
7379
  get position() {
6972
7380
  return this._pos;
@@ -7879,71 +8287,6 @@ async function q5playPreSetup(q) {
7879
8287
  jointStack = [],
7880
8288
  shapeStack = [];
7881
8289
 
7882
- $._syncWorld = () => {
7883
- jointStack = [];
7884
- shapeStack = [];
7885
-
7886
- b2World_Draw(wID, drawCmds.GetDebugDraw());
7887
-
7888
- let cmdPtr = drawCmds.GetCommandsData(),
7889
- cmdSize = drawCmds.GetCommandsSize(),
7890
- cmdStride = drawCmds.GetCommandStride(),
7891
- offset = cmdPtr,
7892
- renderJointForces = $.q5play.renderJointForces,
7893
- s;
7894
-
7895
- for (let i = 0; i < cmdSize; i++, offset += cmdStride) {
7896
- // workaround that unpacks data from
7897
- // the shape material's customColor
7898
- const customColor = Box2D.HEAPU32[(offset + 4) >> 2],
7899
- uid = customColor & 0xffffff,
7900
- isSensor = (customColor >>> 25) & 0x1,
7901
- isFirstShape = (customColor >>> 26) & 0x1;
7902
-
7903
- s = $.q5play.sprites[uid];
7904
-
7905
- let type = Box2D.HEAPU8[offset];
7906
-
7907
- if (type == 7) {
7908
- continue;
7909
- }
7910
-
7911
- let vertexCount = Box2D.HEAPU16[(offset + 8) >> 1];
7912
-
7913
- let dataLen = 4;
7914
- if (type == 1) dataLen = 5 + vertexCount * 2;
7915
- else if (type == 3 || type == 4) dataLen = 5;
7916
- let data = new Float32Array(Box2D.HEAPU8.buffer, offset + 12, dataLen);
7917
-
7918
- if (!s) {
7919
- if (type == 0 && renderJointForces) jointStack.push(data);
7920
- continue;
7921
- }
7922
-
7923
- // always keep position in sync since it has a low performance cost
7924
- // unless the shape is a chain
7925
- if (type < 4 || (type == 4 && !s._hasCapsuleChain)) {
7926
- s._posX = data[0] * meterSize;
7927
- s._posY = data[1] * meterSize;
7928
- }
7929
-
7930
- s._velSynced = false;
7931
- s._vel._magCached = false;
7932
-
7933
- if (!s.visible) {
7934
- continue;
7935
- }
7936
-
7937
- if (s._hasImagery || s._userDefinedDraw) {
7938
- s._rotation = Math.atan2(data[2], data[3]) * RADTODEG;
7939
- }
7940
-
7941
- if (s.debug || (!s._hasImagery && !s._userDefinedDraw)) {
7942
- shapeStack.push({ type, sprite: s, isSensor, isFirstShape, data, vertexCount });
7943
- }
7944
- }
7945
- };
7946
-
7947
8290
  const colorMax = $._colorFormat,
7948
8291
  debugGreen = $.color(0, colorMax, 0, colorMax * 0.9),
7949
8292
  debugGreenFill = $.color(0, colorMax, 0, colorMax * 0.1),
@@ -8242,7 +8585,6 @@ function q5playUpdate() {
8242
8585
 
8243
8586
  if ($.world.autoStep && $.world.timeScale > 0) {
8244
8587
  $.world.physicsUpdate();
8245
- $._syncWorld();
8246
8588
  }
8247
8589
  $.world.autoStep ??= true;
8248
8590
 
@@ -8413,8 +8755,7 @@ attractTo -> es:atraerA
8413
8755
  repelFrom -> es:repelerDe
8414
8756
  applyTorque -> es:aplicarTorque
8415
8757
  angleTo -> es:ánguloHacia
8416
- rotationToFace -> es:rotaciónParaMirar
8417
- angleToFace -> es:ánguloParaMirar
8758
+ angleDistTo -> es:distÁnguloHacia
8418
8759
  setSpeedAndDirection -> es:establecerVelocidadYDirección
8419
8760
  scaleBy -> es:escalarPor
8420
8761
  resetMass -> es:reiniciarMasa