@m2c2kit/core 0.3.14 → 0.3.16

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 (3) hide show
  1. package/dist/index.d.ts +34 -26
  2. package/dist/index.js +292 -133
  3. package/package.json +5 -5
package/dist/index.d.ts CHANGED
@@ -726,12 +726,21 @@ interface CallbackOptions {
726
726
  }
727
727
 
728
728
  /**
729
- * Notifies when events in the Frame cycle occur on a Game.
729
+ * A Plugin is code that can be registered to run at certain points in the game loop.
730
730
  */
731
- interface FrameCycleEvent extends EventBase {
732
- target: Game;
733
- /** difference in milliseconds since the last Frame lifecycle began */
734
- deltaTime: number;
731
+ interface Plugin {
732
+ /** Short identifier of the plugin. */
733
+ id: string;
734
+ /** What kind of m2c2kit object does the plugin work with? */
735
+ type: "Game" | "Session" | "Survey";
736
+ /** Initialization code run when the plugin is registered with the game. */
737
+ initialize?: (game: Game) => Promise<void>;
738
+ /** Is the plugin disabled and not to be run? Default is false. @remarks Disabled plugins will still be initialized. */
739
+ disabled?: boolean;
740
+ /** Plugin code run before the frame update, but before the frame draw. */
741
+ beforeUpdate?: (game: Game, deltaTime: number) => void;
742
+ /** Plugin code run after the frame update, but before the frame draw. */
743
+ afterUpdate?: (game: Game, deltaTime: number) => void;
735
744
  }
736
745
 
737
746
  interface TrialData {
@@ -757,6 +766,7 @@ declare class Game implements Activity {
757
766
  private warmupFunctionQueue;
758
767
  private loaderElementsRemoved;
759
768
  private _dataStores?;
769
+ private plugins;
760
770
  additionalParameters?: unknown;
761
771
  staticTrialSchema: {
762
772
  [key: string]: JsonSchemaDataTypeScriptTypes;
@@ -983,7 +993,7 @@ declare class Game implements Activity {
983
993
  * is not found, then return fallback value
984
994
  *
985
995
  * @param parameterName - the name of the game parameter whose value is requested
986
- * @param fallback - the value to return if parameterName is not found
996
+ * @param fallbackValue - the value to return if parameterName is not found
987
997
  * @returns
988
998
  */
989
999
  getParameterOrFallback<T, U>(parameterName: string, fallbackValue: U): T | U;
@@ -1171,12 +1181,14 @@ declare class Game implements Activity {
1171
1181
  */
1172
1182
  private createOutgoingScene;
1173
1183
  /**
1174
- * Executes a callback when the frame has finished simulating physics.
1184
+ * Registers a plugin with the game.
1175
1185
  *
1176
- * @param callback - function to execute.
1177
- * @param options - options for the callback.
1186
+ * @remarks Upon registration, the plugin's optional asynchronous
1187
+ * `initialize()` method will be called.
1188
+ *
1189
+ * @param plugin - Plugin to register
1178
1190
  */
1179
- onFrameDidSimulatePhysics(callback: (frameCycleEvent: FrameCycleEvent) => void, options?: CallbackOptions): void;
1191
+ registerPlugin(plugin: Plugin): Promise<void>;
1180
1192
  private update;
1181
1193
  private draw;
1182
1194
  private calculateFps;
@@ -1224,17 +1236,6 @@ declare class Game implements Activity {
1224
1236
  private htmlCanvasPointerDownHandler;
1225
1237
  private htmlCanvasPointerUpHandler;
1226
1238
  private htmlCanvasPointerMoveHandler;
1227
- /**
1228
- * Adjusts dragging behavior when the pointer leaves the canvas
1229
- *
1230
- * @remarks This is necessary because the pointerup event is not fired when
1231
- * the pointer leaves the canvas. On desktop, this means that the user might
1232
- * lift the pointer outside the canvas, but the entity will still be dragged
1233
- * when the pointer is moved back into the canvas.
1234
- *
1235
- * @param domPointerEvent
1236
- * @returns
1237
- */
1238
1239
  private htmlCanvasPointerLeaveHandler;
1239
1240
  /**
1240
1241
  * Determines if/how m2c2kit entities respond to the DOM PointerDown event
@@ -1246,6 +1247,7 @@ declare class Game implements Activity {
1246
1247
  private processDomPointerDown;
1247
1248
  private processDomPointerUp;
1248
1249
  private processDomPointerMove;
1250
+ private processDomPointerLeave;
1249
1251
  private raiseM2PointerDownEvent;
1250
1252
  private raiseTapDownEvent;
1251
1253
  private raiseTapLeaveEvent;
@@ -1688,7 +1690,7 @@ interface EventBase {
1688
1690
  /** Type of event. */
1689
1691
  type: EventType | string;
1690
1692
  /** The object on which the event occurred. */
1691
- target: Entity | Session | Activity;
1693
+ target: Entity | Session | Activity | Plugin;
1692
1694
  /** Has the event been taken care of by the listener and not be dispatched to other targets? */
1693
1695
  handled?: boolean;
1694
1696
  }
@@ -1794,10 +1796,10 @@ declare abstract class Entity implements EntityOptions {
1794
1796
  isShape: boolean;
1795
1797
  isText: boolean;
1796
1798
  name: string;
1797
- position: Point;
1798
- scale: number;
1799
+ _position: Point;
1800
+ _scale: number;
1799
1801
  alpha: number;
1800
- zRotation: number;
1802
+ _zRotation: number;
1801
1803
  isUserInteractionEnabled: boolean;
1802
1804
  draggable: boolean;
1803
1805
  hidden: boolean;
@@ -2095,6 +2097,12 @@ declare abstract class Entity implements EntityOptions {
2095
2097
  */
2096
2098
  get canvasKit(): CanvasKit;
2097
2099
  get parentSceneAsEntity(): Entity;
2100
+ get position(): Point;
2101
+ set position(position: Point);
2102
+ get zRotation(): number;
2103
+ set zRotation(zRotation: number);
2104
+ get scale(): number;
2105
+ set scale(scale: number);
2098
2106
  /**
2099
2107
  * For a given directed acyclic graph, topological ordering of the vertices will be identified using BFS
2100
2108
  * @param adjList Adjacency List that represent a graph with vertices and edges
@@ -3263,4 +3271,4 @@ declare class WebGlInfo {
3263
3271
  static dispose(): void;
3264
3272
  }
3265
3273
 
3266
- export { Action, type Activity, type ActivityKeyValueData, type ActivityLifecycleEvent, type ActivityResultsEvent, ActivityType, type BrowserImage, type CallbackOptions, CanvasKitHelpers, ColorfulMutablePath, Composite, type CompositeOptions, Constants, ConstraintType, type Constraints, CustomAction, type CustomActionOptions, type DefaultParameter, Dimensions, type DrawableOptions, type EasingFunction, Easings, Entity, type EntityEvent, type EntityEventListener, type EntityOptions, EntityType, Equals, type EventBase, type EventListenerBase, EventType, FadeAlphaAction, type FadeAlphaActionOptions, type FontData, FontManager, Game, type GameData, type GameOptions, type GameParameters, GlobalVariables, type GoToActivityOptions, GroupAction, I18n, type IDataStore, type IDrawable, type IText, ImageManager, Label, LabelHorizontalAlignmentMode, type LabelOptions, type Layout, LayoutConstraint, LoadedImage, type M2ColorfulPath, type M2DragEvent, type M2Path, type M2PointerEvent, MoveAction, type MoveActionOptions, MutablePath, NoneTransition, type Point, RandomDraws, type RectOptions, type RgbaColor, RotateAction, ScaleAction, type ScaleActionOptions, Scene, type SceneOptions, SceneTransition, SequenceAction, Session, type SessionDictionaryValues, type SessionLifecycleEvent, type SessionOptions, Shape, type ShapeOptions, ShapeType, type Size, SlideTransition, type SlideTransitionOptions, Sprite, type SpriteOptions, Story, type StoryOptions, type TapEvent, TextLine, type TextLineOptions, type TextOptions, Timer, Transition, TransitionDirection, TransitionType, type Translations, type TrialData, type TrialSchema, Uuid, WaitAction, type WaitActionOptions, WebColors, WebGlInfo, handleInterfaceOptions };
3274
+ export { Action, type Activity, type ActivityKeyValueData, type ActivityLifecycleEvent, type ActivityResultsEvent, ActivityType, type BrowserImage, type CallbackOptions, CanvasKitHelpers, ColorfulMutablePath, Composite, type CompositeOptions, Constants, ConstraintType, type Constraints, CustomAction, type CustomActionOptions, type DefaultParameter, Dimensions, type DrawableOptions, type EasingFunction, Easings, Entity, type EntityEvent, type EntityEventListener, type EntityOptions, EntityType, Equals, type EventBase, type EventListenerBase, EventType, FadeAlphaAction, type FadeAlphaActionOptions, type FontData, FontManager, Game, type GameData, type GameOptions, type GameParameters, GlobalVariables, type GoToActivityOptions, GroupAction, I18n, type IDataStore, type IDrawable, type IText, ImageManager, Label, LabelHorizontalAlignmentMode, type LabelOptions, type Layout, LayoutConstraint, LoadedImage, type M2ColorfulPath, type M2DragEvent, type M2Path, type M2PointerEvent, MoveAction, type MoveActionOptions, MutablePath, NoneTransition, type Plugin, type Point, RandomDraws, type RectOptions, type RgbaColor, RotateAction, ScaleAction, type ScaleActionOptions, Scene, type SceneOptions, SceneTransition, SequenceAction, Session, type SessionDictionaryValues, type SessionLifecycleEvent, type SessionOptions, Shape, type ShapeOptions, ShapeType, type Size, SlideTransition, type SlideTransitionOptions, Sprite, type SpriteOptions, Story, type StoryOptions, type TapEvent, TextLine, type TextLineOptions, type TextOptions, Timer, Transition, TransitionDirection, TransitionType, type Translations, type TrialData, type TrialSchema, Uuid, WaitAction, type WaitActionOptions, WebColors, WebGlInfo, handleInterfaceOptions };
package/dist/index.js CHANGED
@@ -175,7 +175,7 @@ class M2c2KitHelpers {
175
175
  entities.push(drawableEntity);
176
176
  const entityPointsArray = entities.map((entity) => {
177
177
  const boundingBox = M2c2KitHelpers.calculateEntityAbsoluteBoundingBox(entity);
178
- return boundingBoxToPoints(boundingBox);
178
+ return M2c2KitHelpers.boundingBoxToPoints(boundingBox);
179
179
  });
180
180
  for (let i = 0; i < entityPointsArray.length; i++) {
181
181
  if (!entityNeedsRotation(entities[i])) {
@@ -183,7 +183,7 @@ class M2c2KitHelpers {
183
183
  }
184
184
  const entityPoints = entityPointsArray[i];
185
185
  const radians = entities[i].zRotation;
186
- const center = findCentroid(entityPoints);
186
+ const center = M2c2KitHelpers.findCentroid(entityPoints);
187
187
  for (let j = i; j < entities.length; j++) {
188
188
  entityPointsArray[j] = rotateRectangle(
189
189
  entityPointsArray[j],
@@ -206,7 +206,7 @@ class M2c2KitHelpers {
206
206
  * @param drawableEntity - Entity to rotate the canvas for
207
207
  */
208
208
  static rotateCanvasForDrawableEntity(canvas, drawableEntity) {
209
- const rotationTransforms = calculateRotationTransforms(drawableEntity);
209
+ const rotationTransforms = M2c2KitHelpers.calculateRotationTransforms(drawableEntity);
210
210
  if (rotationTransforms.length === 0) {
211
211
  return;
212
212
  }
@@ -269,24 +269,167 @@ class M2c2KitHelpers {
269
269
  }
270
270
  return normalized;
271
271
  }
272
+ /**
273
+ * Checks if two points are on the same side of a line.
274
+ *
275
+ * @remarks The line is defined by two points, a and b. The function uses
276
+ * the cross product to determine the relative position of the points.
277
+ *
278
+ * @param p1 - point to check
279
+ * @param p2 - point to check
280
+ * @param a - point that defines one end of the line
281
+ * @param b - point that defines the other end of the line
282
+ * @returns true if p1 and p2 are on the same side of the line, or false
283
+ * otherwise
284
+ */
285
+ static arePointsOnSameSideOfLine(p1, p2, a, b) {
286
+ const cp1 = (b.x - a.x) * (p1.y - a.y) - (b.y - a.y) * (p1.x - a.x);
287
+ const cp2 = (b.x - a.x) * (p2.y - a.y) - (b.y - a.y) * (p2.x - a.x);
288
+ return cp1 * cp2 >= 0;
289
+ }
272
290
  /**
273
291
  * Checks if a point is inside a rectangle.
274
292
  *
293
+ * @remarks The rectangle may have been rotated (sides might not be parallel
294
+ * to the axes).
295
+ *
275
296
  * @param point - The Point to check
276
297
  * @param rect - An array of four Points representing the vertices of the
277
298
  * rectangle in clockwise order
278
299
  * @returns true if the Point is inside the rectangle
279
300
  */
280
301
  static isPointInsideRectangle(point, rect) {
281
- for (let i = 0; i < 4; i++) {
282
- const p1 = rect[i];
283
- const p2 = rect[(i + 1) % 4];
284
- const cross = (p2.x - p1.x) * (point.y - p1.y) - (p2.y - p1.y) * (point.x - p1.x);
285
- if (cross > 0) {
286
- return false;
287
- }
302
+ if (rect.length !== 4) {
303
+ throw new Error("Invalid input: expected an array of four points");
304
+ }
305
+ return M2c2KitHelpers.arePointsOnSameSideOfLine(
306
+ point,
307
+ rect[2],
308
+ rect[0],
309
+ rect[1]
310
+ ) && M2c2KitHelpers.arePointsOnSameSideOfLine(
311
+ point,
312
+ rect[3],
313
+ rect[1],
314
+ rect[2]
315
+ ) && M2c2KitHelpers.arePointsOnSameSideOfLine(
316
+ point,
317
+ rect[0],
318
+ rect[2],
319
+ rect[3]
320
+ ) && M2c2KitHelpers.arePointsOnSameSideOfLine(point, rect[1], rect[3], rect[0]);
321
+ }
322
+ /**
323
+ * Checks if the entity or any of its ancestors have been rotated.
324
+ *
325
+ * @param entity - entity to check
326
+ * @returns true if the entity or any of its ancestors have been rotated
327
+ */
328
+ static entityOrAncestorHasBeenRotated(entity) {
329
+ const entities = entity.ancestors;
330
+ entities.push(entity);
331
+ return entities.some((entity2) => entityNeedsRotation(entity2));
332
+ }
333
+ /**
334
+ * Converts a bounding box to an array of four points representing the
335
+ * vertices of the rectangle.
336
+ *
337
+ * @remarks In m2c2kit, the y-axis is inverted: origin is in the upper-left.
338
+ * Vertices are returned in clockwise order starting from the upper-left.
339
+ *
340
+ * @param boundingBox
341
+ * @returns An array of four points
342
+ */
343
+ static boundingBoxToPoints(boundingBox) {
344
+ const { xMin, xMax, yMin, yMax } = boundingBox;
345
+ const points = [
346
+ { x: xMin, y: yMin },
347
+ // Top left
348
+ { x: xMax, y: yMin },
349
+ // Top right
350
+ { x: xMax, y: yMax },
351
+ // Bottom right
352
+ { x: xMin, y: yMax }
353
+ // Bottom left
354
+ ];
355
+ return points;
356
+ }
357
+ /**
358
+ * Finds the centroid of a rectangle.
359
+ *
360
+ * @param points - An array of four points representing the vertices of the
361
+ * rectangle.
362
+ * @returns array of points representing the centroid of the rectangle.
363
+ */
364
+ static findCentroid(points) {
365
+ if (points.length !== 4) {
366
+ throw new Error("Invalid input: expected an array of four points");
288
367
  }
289
- return true;
368
+ let xSum = 0;
369
+ let ySum = 0;
370
+ for (const point of points) {
371
+ xSum += point.x;
372
+ ySum += point.y;
373
+ }
374
+ const xAvg = xSum / 4;
375
+ const yAvg = ySum / 4;
376
+ return { x: xAvg, y: yAvg };
377
+ }
378
+ /**
379
+ * Rotates a point, counterclockwise, around another point by an angle in
380
+ * radians.
381
+ *
382
+ * @param point - Point to rotate
383
+ * @param radians - angle in radians
384
+ * @param center - Point to rotate around
385
+ * @returns rotated point
386
+ */
387
+ static rotatePoint(point, radians, center) {
388
+ const dx = point.x - center.x;
389
+ const dy = point.y - center.y;
390
+ const x = dx * Math.cos(-radians) - dy * Math.sin(-radians);
391
+ const y = dx * Math.sin(-radians) + dy * Math.cos(-radians);
392
+ return {
393
+ x: x + center.x,
394
+ y: y + center.y
395
+ };
396
+ }
397
+ /**
398
+ * Calculates the rotation transforms to apply to entity, respecting any
399
+ * ancestor rotations.
400
+ *
401
+ * @param drawableEntity - entity to calculate rotation transforms for
402
+ * @returns array of rotation transforms to apply
403
+ */
404
+ static calculateRotationTransforms(drawableEntity) {
405
+ const rotationTransforms = [];
406
+ const entities = drawableEntity.ancestors;
407
+ entities.reverse();
408
+ entities.push(drawableEntity);
409
+ entities.forEach((entity) => {
410
+ if (entityNeedsRotation(entity)) {
411
+ const drawable = entity;
412
+ if (drawable.type === EntityType.Scene) {
413
+ const center2 = {
414
+ x: drawable.absolutePosition.x + drawable.size.width * 0.5,
415
+ y: drawable.absolutePosition.y + drawable.size.height * 0.5
416
+ };
417
+ rotationTransforms.push({
418
+ radians: drawable.zRotation,
419
+ center: center2
420
+ });
421
+ return;
422
+ }
423
+ const boundingBox = M2c2KitHelpers.calculateEntityAbsoluteBoundingBox(drawable);
424
+ const points = M2c2KitHelpers.boundingBoxToPoints(boundingBox);
425
+ const center = M2c2KitHelpers.findCentroid(points);
426
+ rotationTransforms.push({
427
+ radians: drawable.zRotation,
428
+ center
429
+ });
430
+ }
431
+ });
432
+ return rotationTransforms;
290
433
  }
291
434
  }
292
435
  function applyRotationTransformsToCanvas(rotationTransforms, scale, canvas) {
@@ -298,84 +441,16 @@ function applyRotationTransformsToCanvas(rotationTransforms, scale, canvas) {
298
441
  );
299
442
  });
300
443
  }
301
- function calculateRotationTransforms(drawableEntity) {
302
- const rotationTransforms = [];
303
- const entities = drawableEntity.ancestors;
304
- entities.reverse();
305
- entities.push(drawableEntity);
306
- entities.forEach((entity) => {
307
- if (entityNeedsRotation(entity)) {
308
- const drawable = entity;
309
- if (drawable.type === EntityType.Scene) {
310
- const center2 = {
311
- x: drawable.absolutePosition.x + drawable.size.width * 0.5,
312
- y: drawable.absolutePosition.y + drawable.size.height * 0.5
313
- };
314
- rotationTransforms.push({
315
- radians: drawable.zRotation,
316
- center: center2
317
- });
318
- return;
319
- }
320
- const boundingBox = M2c2KitHelpers.calculateEntityAbsoluteBoundingBox(drawable);
321
- const points = boundingBoxToPoints(boundingBox);
322
- const center = findCentroid(points);
323
- rotationTransforms.push({
324
- radians: drawable.zRotation,
325
- center
326
- });
327
- }
328
- });
329
- return rotationTransforms;
330
- }
331
444
  function entityNeedsRotation(entity) {
332
445
  return M2c2KitHelpers.normalizeAngleRadians(entity.zRotation) !== 0 && entity.isDrawable;
333
446
  }
334
- function boundingBoxToPoints(boundingBox) {
335
- const { xMin, xMax, yMin, yMax } = boundingBox;
336
- const points = [
337
- { x: xMin, y: yMin },
338
- // Top left
339
- { x: xMin, y: yMax },
340
- // Bottom left
341
- { x: xMax, y: yMax },
342
- // Bottom right
343
- { x: xMax, y: yMin }
344
- // Top right
345
- ];
346
- return points;
347
- }
348
- function findCentroid(points) {
349
- if (points.length !== 4) {
350
- throw new Error("Invalid input: expected an array of four points");
351
- }
352
- let xSum = 0;
353
- let ySum = 0;
354
- for (const point of points) {
355
- xSum += point.x;
356
- ySum += point.y;
357
- }
358
- const xAvg = xSum / 4;
359
- const yAvg = ySum / 4;
360
- return { x: xAvg, y: yAvg };
361
- }
362
- function rotatePoint(point, radians, center) {
363
- const dx = point.x - center.x;
364
- const dy = point.y - center.y;
365
- const x = dx * Math.cos(-radians) - dy * Math.sin(-radians);
366
- const y = dx * Math.sin(-radians) + dy * Math.cos(-radians);
367
- return {
368
- x: x + center.x,
369
- y: y + center.y
370
- };
371
- }
372
447
  function rotateRectangle(rect, radians, center) {
373
448
  if (rect.length !== 4) {
374
449
  throw new Error("Invalid input: expected an array of four points");
375
450
  }
376
451
  const rotated = [];
377
452
  for (const p of rect) {
378
- rotated.push(rotatePoint(p, radians, center));
453
+ rotated.push(M2c2KitHelpers.rotatePoint(p, radians, center));
379
454
  }
380
455
  return rotated;
381
456
  }
@@ -1440,11 +1515,11 @@ class Entity {
1440
1515
  this.isDrawable = false;
1441
1516
  this.isShape = false;
1442
1517
  this.isText = false;
1443
- this.position = { x: 0, y: 0 };
1518
+ this._position = { x: 0, y: 0 };
1444
1519
  // position of the entity in the parent coordinate system
1445
- this.scale = 1;
1520
+ this._scale = 1;
1446
1521
  this.alpha = 1;
1447
- this.zRotation = 0;
1522
+ this._zRotation = 0;
1448
1523
  this.isUserInteractionEnabled = false;
1449
1524
  this.draggable = false;
1450
1525
  this.hidden = false;
@@ -2277,6 +2352,38 @@ class Entity {
2277
2352
  }
2278
2353
  throw new Error(`Entity ${this} has not been added to a scene`);
2279
2354
  }
2355
+ get position() {
2356
+ const entity = this;
2357
+ return {
2358
+ get x() {
2359
+ return entity._position.x;
2360
+ },
2361
+ set x(x) {
2362
+ entity._position.x = x;
2363
+ },
2364
+ get y() {
2365
+ return entity._position.y;
2366
+ },
2367
+ set y(y) {
2368
+ entity._position.y = y;
2369
+ }
2370
+ };
2371
+ }
2372
+ set position(position) {
2373
+ this._position = position;
2374
+ }
2375
+ get zRotation() {
2376
+ return this._zRotation;
2377
+ }
2378
+ set zRotation(zRotation) {
2379
+ this._zRotation = zRotation;
2380
+ }
2381
+ get scale() {
2382
+ return this._scale;
2383
+ }
2384
+ set scale(scale) {
2385
+ this._scale = scale;
2386
+ }
2280
2387
  // from https://medium.com/@konduruharish/topological-sort-in-typescript-and-c-6d5ecc4bad95
2281
2388
  /**
2282
2389
  * For a given directed acyclic graph, topological ordering of the vertices will be identified using BFS
@@ -3595,6 +3702,7 @@ class Game {
3595
3702
  this.steppingNow = 0;
3596
3703
  this.warmupFunctionQueue = new Array();
3597
3704
  this.loaderElementsRemoved = false;
3705
+ this.plugins = [];
3598
3706
  this.staticTrialSchema = {};
3599
3707
  this.data = {
3600
3708
  trials: new Array()
@@ -4042,7 +4150,7 @@ class Game {
4042
4150
  * is not found, then return fallback value
4043
4151
  *
4044
4152
  * @param parameterName - the name of the game parameter whose value is requested
4045
- * @param fallback - the value to return if parameterName is not found
4153
+ * @param fallbackValue - the value to return if parameterName is not found
4046
4154
  * @returns
4047
4155
  */
4048
4156
  getParameterOrFallback(parameterName, fallbackValue) {
@@ -5037,27 +5145,34 @@ class Game {
5037
5145
  return outgoingScene;
5038
5146
  }
5039
5147
  /**
5040
- * Executes a callback when the frame has finished simulating physics.
5148
+ * Registers a plugin with the game.
5041
5149
  *
5042
- * @param callback - function to execute.
5043
- * @param options - options for the callback.
5150
+ * @remarks Upon registration, the plugin's optional asynchronous
5151
+ * `initialize()` method will be called.
5152
+ *
5153
+ * @param plugin - Plugin to register
5044
5154
  */
5045
- onFrameDidSimulatePhysics(callback, options) {
5046
- this.addEventListener(
5047
- EventType.FrameDidSimulatePhysics,
5048
- callback,
5049
- options
5050
- );
5155
+ async registerPlugin(plugin) {
5156
+ if (plugin.type !== ActivityType.Game) {
5157
+ throw new Error(
5158
+ `registerPlugin(): plugin ${plugin.id} is not a game plugin. It is a ${plugin.type} plugin.`
5159
+ );
5160
+ }
5161
+ if (this.plugins.includes(plugin) || this.plugins.map((p) => p.id).includes(plugin.id)) {
5162
+ throw new Error(
5163
+ `registerPlugin(): plugin ${plugin.id} already registered.`
5164
+ );
5165
+ }
5166
+ this.plugins.push(plugin);
5167
+ if (plugin.initialize) {
5168
+ await plugin.initialize(this);
5169
+ }
5051
5170
  }
5052
5171
  update() {
5172
+ this.plugins.filter((p) => p.beforeUpdate !== void 0 && p.disabled !== true).forEach((p) => p.beforeUpdate(this, Globals.deltaTime));
5053
5173
  this.scenes.filter((scene) => scene._active).forEach((scene) => scene.update());
5054
5174
  this.freeEntitiesScene.update();
5055
- const frameDidSimulatePhysicsEvent = {
5056
- target: this,
5057
- type: EventType.FrameDidSimulatePhysics,
5058
- deltaTime: Globals.deltaTime
5059
- };
5060
- this.raiseActivityEventOnListeners(frameDidSimulatePhysicsEvent);
5175
+ this.plugins.filter((p) => p.afterUpdate !== void 0 && p.disabled !== true).forEach((p) => p.afterUpdate(this, Globals.deltaTime));
5061
5176
  }
5062
5177
  draw(canvas) {
5063
5178
  this.scenes.filter((scene) => scene._active).forEach((scene) => scene.draw(canvas));
@@ -5471,35 +5586,26 @@ class Game {
5471
5586
  domPointerEvent
5472
5587
  );
5473
5588
  }
5474
- /**
5475
- * Adjusts dragging behavior when the pointer leaves the canvas
5476
- *
5477
- * @remarks This is necessary because the pointerup event is not fired when
5478
- * the pointer leaves the canvas. On desktop, this means that the user might
5479
- * lift the pointer outside the canvas, but the entity will still be dragged
5480
- * when the pointer is moved back into the canvas.
5481
- *
5482
- * @param domPointerEvent
5483
- * @returns
5484
- */
5485
5589
  htmlCanvasPointerLeaveHandler(domPointerEvent) {
5486
5590
  if (!this.currentScene) {
5487
5591
  return;
5488
5592
  }
5489
- this.currentScene.children.forEach((entity) => {
5490
- if (entity.dragging) {
5491
- const m2Event = {
5492
- target: entity,
5493
- type: EventType.DragEnd,
5494
- handled: false
5495
- };
5496
- entity.dragging = false;
5497
- entity.pressed = false;
5498
- entity.pressedAndWithinHitArea = false;
5499
- this.raiseM2DragEndEvent(entity, m2Event, domPointerEvent);
5500
- return;
5501
- }
5502
- });
5593
+ domPointerEvent.preventDefault();
5594
+ const scene = this.currentScene;
5595
+ if (!scene || !this.sceneCanReceiveUserInteraction(scene)) {
5596
+ return;
5597
+ }
5598
+ const m2Event = {
5599
+ target: scene,
5600
+ type: EventType.PointerLeave,
5601
+ handled: false
5602
+ };
5603
+ this.processDomPointerLeave(scene, m2Event, domPointerEvent);
5604
+ this.processDomPointerLeave(
5605
+ this.freeEntitiesScene,
5606
+ m2Event,
5607
+ domPointerEvent
5608
+ );
5503
5609
  }
5504
5610
  /**
5505
5611
  * Determines if/how m2c2kit entities respond to the DOM PointerDown event
@@ -5612,7 +5718,7 @@ class Game {
5612
5718
  entity.pressedAndWithinHitArea = false;
5613
5719
  this.raiseTapLeaveEvent(entity, m2Event, domPointerEvent);
5614
5720
  }
5615
- if (this.IsCanvasPointWithinEntityBounds(
5721
+ if (entity.isUserInteractionEnabled && this.IsCanvasPointWithinEntityBounds(
5616
5722
  entity,
5617
5723
  domPointerEvent.offsetX,
5618
5724
  domPointerEvent.offsetY
@@ -5620,15 +5726,13 @@ class Game {
5620
5726
  this.raiseM2PointerMoveEvent(entity, m2Event, domPointerEvent);
5621
5727
  entity.withinHitArea = true;
5622
5728
  }
5623
- if (!this.IsCanvasPointWithinEntityBounds(
5729
+ if (entity.isUserInteractionEnabled && entity.withinHitArea && !this.IsCanvasPointWithinEntityBounds(
5624
5730
  entity,
5625
5731
  domPointerEvent.offsetX,
5626
5732
  domPointerEvent.offsetY
5627
5733
  )) {
5628
- if (entity.withinHitArea) {
5629
- this.raiseM2PointerLeaveEvent(entity, m2Event, domPointerEvent);
5630
- entity.withinHitArea = false;
5631
- }
5734
+ entity.withinHitArea = false;
5735
+ this.raiseM2PointerLeaveEvent(entity, m2Event, domPointerEvent);
5632
5736
  }
5633
5737
  if (entity.children) {
5634
5738
  entity.children.filter((entity2) => !entity2.hidden).filter((entity2) => entity2.isDrawable).sort(
@@ -5638,6 +5742,46 @@ class Game {
5638
5742
  );
5639
5743
  }
5640
5744
  }
5745
+ processDomPointerLeave(entity, m2Event, domPointerEvent) {
5746
+ if (m2Event.handled) {
5747
+ return;
5748
+ }
5749
+ if (entity.dragging) {
5750
+ const m2Event2 = {
5751
+ target: entity,
5752
+ type: EventType.DragEnd,
5753
+ handled: false
5754
+ };
5755
+ entity.dragging = false;
5756
+ entity.pressed = false;
5757
+ entity.pressedAndWithinHitArea = false;
5758
+ this.raiseM2DragEndEvent(entity, m2Event2, domPointerEvent);
5759
+ return;
5760
+ }
5761
+ if (entity.isUserInteractionEnabled && entity.pressed && entity.pressedAndWithinHitArea && !this.IsCanvasPointWithinEntityBounds(
5762
+ entity,
5763
+ domPointerEvent.offsetX,
5764
+ domPointerEvent.offsetY
5765
+ )) {
5766
+ entity.pressedAndWithinHitArea = false;
5767
+ this.raiseTapLeaveEvent(entity, m2Event, domPointerEvent);
5768
+ }
5769
+ if (entity.isUserInteractionEnabled && entity.withinHitArea && !this.IsCanvasPointWithinEntityBounds(
5770
+ entity,
5771
+ domPointerEvent.offsetX,
5772
+ domPointerEvent.offsetY
5773
+ )) {
5774
+ entity.withinHitArea = false;
5775
+ this.raiseM2PointerLeaveEvent(entity, m2Event, domPointerEvent);
5776
+ }
5777
+ if (entity.children) {
5778
+ entity.children.filter((entity2) => !entity2.hidden).filter((entity2) => entity2.isDrawable).sort(
5779
+ (a, b) => b.zPosition - a.zPosition
5780
+ ).forEach(
5781
+ (entity2) => this.processDomPointerLeave(entity2, m2Event, domPointerEvent)
5782
+ );
5783
+ }
5784
+ }
5641
5785
  raiseM2PointerDownEvent(entity, m2Event, domPointerEvent) {
5642
5786
  m2Event.target = entity;
5643
5787
  m2Event.type = EventType.PointerDown;
@@ -5748,9 +5892,24 @@ class Game {
5748
5892
  width = radius * 2;
5749
5893
  height = radius * 2;
5750
5894
  }
5751
- const x = domPointerEvent.offsetX;
5752
- const y = domPointerEvent.offsetY;
5895
+ let x = domPointerEvent.offsetX;
5896
+ let y = domPointerEvent.offsetY;
5753
5897
  const bb = M2c2KitHelpers.calculateEntityAbsoluteBoundingBox(entity);
5898
+ if (M2c2KitHelpers.entityOrAncestorHasBeenRotated(entity)) {
5899
+ const transforms = M2c2KitHelpers.calculateRotationTransforms(
5900
+ entity
5901
+ );
5902
+ transforms.forEach((transform) => {
5903
+ const rotatedPoint = M2c2KitHelpers.rotatePoint(
5904
+ { x, y },
5905
+ // take negative because we are applying the reverse rotation
5906
+ -transform.radians,
5907
+ transform.center
5908
+ );
5909
+ x = rotatedPoint.x;
5910
+ y = rotatedPoint.y;
5911
+ });
5912
+ }
5754
5913
  const relativeX = (x - bb.xMin) / (bb.xMax - bb.xMin) * width;
5755
5914
  const relativeY = (y - bb.yMin) / (bb.yMax - bb.yMin) * height;
5756
5915
  return { x: relativeX, y: relativeY };
@@ -7332,7 +7491,7 @@ class Session {
7332
7491
  this.eventListeners = new Array();
7333
7492
  this.sessionDictionary = /* @__PURE__ */ new Map();
7334
7493
  this.initialized = false;
7335
- this.version = "0.3.14 (60325bea)";
7494
+ this.version = "0.3.16 (d8a00e86)";
7336
7495
  this.options = options;
7337
7496
  for (const activity of this.options.activities) {
7338
7497
  if (this.options.activities.filter((a) => a === activity).length > 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m2c2kit/core",
3
- "version": "0.3.14",
3
+ "version": "0.3.16",
4
4
  "description": "The m2c2kit core functionality",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "@rollup/plugin-commonjs": "25.0.7",
37
37
  "@rollup/plugin-node-resolve": "15.2.3",
38
38
  "@rollup/plugin-replace": "5.0.5",
39
- "@types/jest": "29.5.10",
39
+ "@types/jest": "29.5.11",
40
40
  "@types/jsdom": "21.1.6",
41
41
  "@webgpu/types": "0.1.40",
42
42
  "canvas": "2.11.2",
@@ -48,14 +48,14 @@
48
48
  "jest-environment-jsdom": "29.7.0",
49
49
  "jsdom": "23.0.1",
50
50
  "rimraf": "5.0.5",
51
- "rollup": "4.6.1",
51
+ "rollup": "4.9.2",
52
52
  "rollup-plugin-copy": "3.5.0",
53
53
  "rollup-plugin-dts": "6.1.0",
54
54
  "rollup-plugin-esbuild": "6.1.0",
55
55
  "rollup-plugin-polyfill-node": "0.13.0",
56
56
  "ts-jest": "29.1.1",
57
- "ts-node": "10.9.1",
58
- "typescript": "5.3.2"
57
+ "ts-node": "10.9.2",
58
+ "typescript": "5.3.3"
59
59
  },
60
60
  "engines": {
61
61
  "node": ">=18"