@cyclonium/physics-2d 0.0.103 → 0.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.
@@ -156,8 +156,7 @@ let KinematicCharacterController2D = class KinematicCharacterController2D extend
156
156
  else {
157
157
  const currentPosition = fromPx2ImplVec2(rigidBody.impl.nextTranslation());
158
158
  const newPosition = fromPx2ImplVec2(currentPosition).add(movement);
159
- Transform2DComponent.of(collider).position = newPosition;
160
- rigidBody.impl.setNextKinematicTranslation(toPx2ImplVec2(newPosition));
159
+ rigidBody.setNextKinematicPosition(newPosition);
161
160
  }
162
161
  this._emitCharacterCollisionEvents(impl, world);
163
162
  }
@@ -12,37 +12,12 @@ export declare abstract class Collider2D extends PhysicsComponent2DBase {
12
12
  get center(): Vec2;
13
13
  set center(value: Vec2);
14
14
  get bounds(): Bounds2D;
15
- /**
16
- * Test only.
17
- * @internal
18
- */
19
- get impl_internal(): px2Impl.Collider | undefined;
20
15
  onContactBegin: import("@cyclonium/event").ManagedEventEmitter<[Collider2D, Collider2D, Contact2DInfo]>;
21
16
  onContactStay: import("@cyclonium/event").ManagedEventEmitter<[Collider2D, Collider2D, Contact2DInfo]>;
22
17
  onContactEnd: import("@cyclonium/event").ManagedEventEmitter<[Collider2D, Collider2D, Contact2DInfo]>;
23
18
  intersect(opts: ShapeIntersectionQueryOptions): ColliderShapeIntersection | undefined;
24
19
  cast(opts: ShapeCastQueryOptions): ColliderShapeCastHit | undefined;
25
20
  castRay(ray: Ray2D, maxDistance: number, solid?: boolean): RayColliderIntersection | undefined;
26
- /**
27
- * @internal
28
- */
29
- _enableCollisionEventsSinceBody(value: boolean): void;
30
- /**
31
- * @internal
32
- */
33
- _responseToBodyActiveCollisionTypes(value: number): void;
34
- /**
35
- * @internal
36
- */
37
- _responseToBodyCollisionGroups(value: number): void;
38
- /**
39
- * @internal
40
- */
41
- _responseToBodySolverGroups(value: number): void;
42
- /**
43
- * @internal
44
- */
45
- _responseToAttachedRigidBodyChanged(): void;
46
21
  protected get _implCollider(): px2Impl.Collider | undefined;
47
22
  protected recreateCollider(): void;
48
23
  protected onAttachToWorld(): void;
@@ -2,6 +2,7 @@ import { Asset } from 'cc';
2
2
  import { CollisionMatrix } from './collision-matrix.js';
3
3
  export declare class Physics2DSettings extends Asset {
4
4
  fps: number;
5
+ maxSubsteps: number;
5
6
  get tags(): Record<string, number>;
6
7
  private get tags_editor();
7
8
  private set tags_editor(value);
@@ -4,13 +4,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { Asset } from 'cc';
7
+ import { Asset, CCInteger } from 'cc';
8
8
  import { editable, serializable } from '@cyclonium/core/legacy-decorator';
9
9
  import { cycloBuiltinClass } from '@cyclonium/core/internal';
10
10
  import { CollisionMatrix } from './collision-matrix.js';
11
11
  import { dumpRaw } from '@cyclonium/core/utils';
12
12
  let Physics2DSettings = class Physics2DSettings extends Asset {
13
13
  fps = 60;
14
+ maxSubsteps = 4;
14
15
  get tags() {
15
16
  return this._tags;
16
17
  }
@@ -31,8 +32,12 @@ let Physics2DSettings = class Physics2DSettings extends Asset {
31
32
  };
32
33
  __decorate([
33
34
  serializable,
34
- editable
35
+ editable({ min: 1 })
35
36
  ], Physics2DSettings.prototype, "fps", void 0);
37
+ __decorate([
38
+ serializable,
39
+ editable({ type: CCInteger, min: 1, step: 1 })
40
+ ], Physics2DSettings.prototype, "maxSubsteps", void 0);
36
41
  __decorate([
37
42
  editable,
38
43
  dumpRaw
@@ -1,6 +1,6 @@
1
1
  import { CycloComponent } from '@cyclonium/core/framework';
2
- import { PhysicsWorld2D } from './physics-world-2d.js';
3
2
  import { Physics2DSettings } from './physics-2d-settings.js';
3
+ import { PhysicsWorld2D } from './physics-world-2d.js';
4
4
  export declare class PhysicsWorld2DSceneComponent extends CycloComponent {
5
5
  get debug(): boolean;
6
6
  set debug(v: boolean);
@@ -13,6 +13,9 @@ export declare class PhysicsWorld2DSceneComponent extends CycloComponent {
13
13
  private _physicsWorld;
14
14
  private _physicsDebugger;
15
15
  private _lastUpdateFrame;
16
+ private _maxSubsteps;
17
+ private _timeAccumulator;
18
+ private _overloading;
16
19
  private _debug;
17
20
  private _settings;
18
21
  private _updateFrame;
@@ -6,13 +6,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import { EDITOR_NOT_IN_PREVIEW } from 'cc/env';
8
8
  import { editable, executionOrder, idem, serializable } from '@cyclonium/core/legacy-decorator';
9
- import { CycloComponent } from '@cyclonium/core/framework';
10
- import { PhysicsWorld2D } from './physics-world-2d.js';
11
- import { Physics2DDebugger } from '#physics-2d-debugger';
12
- import { PredefinedExecutionOrder } from '@cyclonium/core/framework';
9
+ import { CycloComponent, PredefinedExecutionOrder } from '@cyclonium/core/framework';
13
10
  import { director } from 'cc';
14
- import { cycloBuiltinClass } from '@cyclonium/core/internal';
11
+ import { cycloBuiltinClass, TimeAccumulator } from '@cyclonium/core/internal';
12
+ import { logger } from '@cyclonium/core/log';
13
+ import { Physics2DDebugger } from '#physics-2d-debugger';
15
14
  import { Physics2DSettings } from './physics-2d-settings.js';
15
+ import { PhysicsWorld2D } from './physics-world-2d.js';
16
+ const getDefaultSettings = (() => {
17
+ let defaultSettings;
18
+ return () => defaultSettings ??= new Physics2DSettings();
19
+ })();
16
20
  let PhysicsWorld2DSceneComponent = class PhysicsWorld2DSceneComponent extends CycloComponent {
17
21
  get debug() {
18
22
  return this._debug;
@@ -35,7 +39,19 @@ let PhysicsWorld2DSceneComponent = class PhysicsWorld2DSceneComponent extends Cy
35
39
  }
36
40
  onAwake() {
37
41
  if (!EDITOR_NOT_IN_PREVIEW) {
38
- const settings = this._settings ?? new Physics2DSettings();
42
+ const settings = this._settings ?? getDefaultSettings();
43
+ let fps = settings.fps;
44
+ let maxSubsteps = settings.maxSubsteps;
45
+ if (!Number.isFinite(fps) || fps <= 0) {
46
+ logger.error(`Invalid Physics2DSettings.fps (${fps});`);
47
+ fps = getDefaultSettings().fps;
48
+ }
49
+ if (!Number.isInteger(maxSubsteps) || maxSubsteps <= 0) {
50
+ logger.error(`Invalid Physics2DSettings.maxSubsteps (${maxSubsteps});`);
51
+ maxSubsteps = getDefaultSettings().maxSubsteps;
52
+ }
53
+ this._timeAccumulator = new TimeAccumulator(1 / fps);
54
+ this._maxSubsteps = maxSubsteps;
39
55
  this._physicsWorld = new PhysicsWorld2D({
40
56
  scene: this.node.scene,
41
57
  tags: settings.tags,
@@ -69,9 +85,12 @@ let PhysicsWorld2DSceneComponent = class PhysicsWorld2DSceneComponent extends Cy
69
85
  _physicsWorld = null;
70
86
  _physicsDebugger = undefined;
71
87
  _lastUpdateFrame = -1;
88
+ _maxSubsteps = 4;
89
+ _timeAccumulator = new TimeAccumulator(1 / 60);
90
+ _overloading = false;
72
91
  _debug = false;
73
92
  _settings = null;
74
- _updateFrame(_deltaTime) {
93
+ _updateFrame(deltaTime) {
75
94
  const world = this._physicsWorld;
76
95
  if (!world) {
77
96
  return;
@@ -82,7 +101,17 @@ let PhysicsWorld2DSceneComponent = class PhysicsWorld2DSceneComponent extends Cy
82
101
  this._lastUpdateFrame = actualFrame;
83
102
  world.setOutdated();
84
103
  }
85
- world.step(1 / 60);
104
+ const timeAccumulator = this._timeAccumulator;
105
+ const fixedDeltaTime = timeAccumulator.timeStep;
106
+ const maxSubsteps = this._maxSubsteps;
107
+ const actualSubsteps = timeAccumulator.advance(deltaTime, maxSubsteps);
108
+ const maximumDeltaTime = maxSubsteps * fixedDeltaTime;
109
+ const overloading = deltaTime > maximumDeltaTime;
110
+ if (overloading && !this._overloading) {
111
+ logger.warn(`PhysicsWorld2D clamped an update to the ${maxSubsteps}-substep limit.`);
112
+ }
113
+ this._overloading = overloading;
114
+ world.advanceSubsteps_internal(fixedDeltaTime, actualSubsteps);
86
115
  this._physicsDebugger?.render();
87
116
  }
88
117
  };
@@ -41,7 +41,8 @@ export declare class PhysicsWorld2D {
41
41
  get listenersOnWillDestroy(): import("@cyclonium/event").EventListenerRegistry<[]>;
42
42
  get impl(): px2Impl.World;
43
43
  destroy(): void;
44
- step(_deltaTime: number): void;
44
+ step(deltaTime: number): void;
45
+ private _step;
45
46
  setOutdated(): void;
46
47
  getTagId(tag: string): number;
47
48
  intersectionWithPoint(point: Vec2, filter: SceneQueryFilter): Generator<Collider2D, void, unknown>;
@@ -64,7 +64,22 @@ export class PhysicsWorld2D {
64
64
  this._worldImpl.free();
65
65
  this._worldImpl = null;
66
66
  }
67
- step(_deltaTime) {
67
+ step(deltaTime) {
68
+ this.advanceSubsteps_internal(deltaTime, 1);
69
+ }
70
+ /**
71
+ * Advances a batch of physics steps where `deltaTime` is the length of each substep.
72
+ * @internal
73
+ */
74
+ advanceSubsteps_internal(deltaTime, substepCount) {
75
+ this._worldImpl.timestep = deltaTime;
76
+ for (let i = 0; i < substepCount; i++) {
77
+ const remainingSubsteps = substepCount - i;
78
+ const stepFraction = 1 / remainingSubsteps;
79
+ this._step(stepFraction, remainingSubsteps === 1);
80
+ }
81
+ }
82
+ _step(stepFraction, isLastSubstep) {
68
83
  const outdated = this._outDated;
69
84
  this._outDated = false;
70
85
  for (const component of this._physicsComponents) {
@@ -73,6 +88,12 @@ export class PhysicsWorld2D {
73
88
  }
74
89
  invokeOnSyncTransforms(component, outdated, outdated);
75
90
  }
91
+ for (const { component } of this._rigidBodies.values()) {
92
+ if (!component.enabled) {
93
+ continue;
94
+ }
95
+ component.applyKinematicTargetForStep_internal(stepFraction, isLastSubstep);
96
+ }
76
97
  this._worldImpl.step(this._eventQueue);
77
98
  for (const component of this._physicsComponents) {
78
99
  if (!component.enabled) {
@@ -36,7 +36,9 @@ export declare class RigidBody2D extends PhysicsComponent2DBase {
36
36
  set linearVelocity(value: Vec2);
37
37
  hasTag(tag: string): boolean;
38
38
  addTag(tag: string): void;
39
+ /** Sets the position target reached across the next physics step batch. */
39
40
  setNextKinematicPosition(position: Vec2): void;
41
+ /** Sets the rotation target reached across the next physics step batch. */
40
42
  setNextKinematicRotation(rotation: number): void;
41
43
  get impl(): px2Impl.RigidBody | undefined;
42
44
  protected onDestroy(): void;
@@ -56,6 +58,11 @@ export declare class RigidBody2D extends PhysicsComponent2DBase {
56
58
  private _physicsPosition;
57
59
  private _physicsRotation;
58
60
  private _linearVelocity;
61
+ private _kinematicPositionTarget;
62
+ private _substepKinematicPositionTarget;
63
+ private _kinematicRotationTarget;
64
+ private _hasKinematicPositionTarget;
65
+ private _hasKinematicRotationTarget;
59
66
  private _listenerFlags;
60
67
  private _transformChangeFlagsObserver;
61
68
  private get _transform();
@@ -15,8 +15,8 @@ import { createCollisionEventEmitter, ContactEventListenerFlagIndex } from './sh
15
15
  import { CCString } from 'cc';
16
16
  import { EDITOR_NOT_IN_PREVIEW } from 'cc/env';
17
17
  import { cycloBuiltinClass } from '@cyclonium/core/internal';
18
- import { approxEqual } from '@cyclonium/core/math/number';
19
- import { to0ToPI2 } from '@cyclonium/core/math/trigonometry';
18
+ import { approxEqual, lerp } from '@cyclonium/core/math/number';
19
+ import { lerpAngle, to0ToPI2 } from '@cyclonium/core/math/trigonometry';
20
20
  export var RigidBody2DType;
21
21
  (function (RigidBody2DType) {
22
22
  RigidBody2DType["fixed"] = "fixed";
@@ -141,10 +141,16 @@ let RigidBody2D = class RigidBody2D extends PhysicsComponent2DBase {
141
141
  this._tags.push(tag);
142
142
  }
143
143
  }
144
+ /** Sets the position target reached across the next physics step batch. */
144
145
  setNextKinematicPosition(position) {
146
+ this._kinematicPositionTarget.copyFrom(position);
147
+ this._hasKinematicPositionTarget = true;
145
148
  this._rigidBodyControlBlock?.impl.setNextKinematicTranslation(position);
146
149
  }
150
+ /** Sets the rotation target reached across the next physics step batch. */
147
151
  setNextKinematicRotation(rotation) {
152
+ this._kinematicRotationTarget = rotation;
153
+ this._hasKinematicRotationTarget = true;
148
154
  this._rigidBodyControlBlock?.impl.setNextKinematicRotation(rotation);
149
155
  }
150
156
  get impl() {
@@ -188,6 +194,35 @@ let RigidBody2D = class RigidBody2D extends PhysicsComponent2DBase {
188
194
  onSyncTransforms(forceTransform, _forceScale) {
189
195
  this._syncToPhysics(forceTransform);
190
196
  }
197
+ /** @internal */
198
+ applyKinematicTargetForStep_internal(stepFraction, isLastSubstep) {
199
+ const implBody = this._rigidBodyControlBlock?.impl;
200
+ if (!implBody || this._type !== RigidBody2DType.kinematicPositionBased) {
201
+ return;
202
+ }
203
+ if (this._hasKinematicPositionTarget) {
204
+ const substepTarget = this._substepKinematicPositionTarget;
205
+ if (isLastSubstep) {
206
+ substepTarget.copyFrom(this._kinematicPositionTarget);
207
+ this._hasKinematicPositionTarget = false;
208
+ }
209
+ else {
210
+ const position = this._physicsPosition;
211
+ const target = this._kinematicPositionTarget;
212
+ substepTarget.set(lerp(position.x, target.x, stepFraction), lerp(position.y, target.y, stepFraction));
213
+ }
214
+ implBody.setNextKinematicTranslation(substepTarget);
215
+ }
216
+ if (this._hasKinematicRotationTarget) {
217
+ const substepTarget = isLastSubstep
218
+ ? this._kinematicRotationTarget
219
+ : lerpAngle(this._physicsRotation, this._kinematicRotationTarget, stepFraction);
220
+ if (isLastSubstep) {
221
+ this._hasKinematicRotationTarget = false;
222
+ }
223
+ implBody.setNextKinematicRotation(substepTarget);
224
+ }
225
+ }
191
226
  onAfterPhysicsStep() {
192
227
  this._syncFromPhysics();
193
228
  }
@@ -200,6 +235,11 @@ let RigidBody2D = class RigidBody2D extends PhysicsComponent2DBase {
200
235
  _physicsPosition = new Vec2();
201
236
  _physicsRotation = 0.0;
202
237
  _linearVelocity = new Vec2();
238
+ _kinematicPositionTarget = new Vec2();
239
+ _substepKinematicPositionTarget = new Vec2();
240
+ _kinematicRotationTarget = 0;
241
+ _hasKinematicPositionTarget = false;
242
+ _hasKinematicRotationTarget = false;
203
243
  _listenerFlags = 0;
204
244
  _transformChangeFlagsObserver = new TransformChangeFlagsObserver();
205
245
  get _transform() { return undefined; }
@@ -275,6 +315,8 @@ let RigidBody2D = class RigidBody2D extends PhysicsComponent2DBase {
275
315
  this._rigidBodyControlBlock = null;
276
316
  this._physicsPosition.set(0, 0);
277
317
  this._physicsRotation = 0.0;
318
+ this._hasKinematicPositionTarget = false;
319
+ this._hasKinematicRotationTarget = false;
278
320
  }
279
321
  _setPhysicsPosition(position) {
280
322
  this._physicsPosition.copyFrom(position);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonium/physics-2d",
3
- "version": "0.0.103",
3
+ "version": "0.1.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/shrinktofit/cyclonium",
@@ -25,13 +25,13 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@cyclonium/algorithm": "1.0.0",
28
- "@cyclonium/debug-draw": "0.0.103",
28
+ "@cyclonium/debug-draw": "0.0.105",
29
29
  "@cyclonium/event": "0.0.103",
30
30
  "@cyclonium/rapier2d": "0.0.102",
31
31
  "@cyclonium/web-assembly": "0.0.102"
32
32
  },
33
33
  "peerDependencies": {
34
- "@cyclonium/core": "0.0.103"
34
+ "@cyclonium/core": "0.0.105"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/fs-extra": "^11.0.4",