mage-engine 3.24.3 → 3.24.4

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 (2) hide show
  1. package/dist/mage.js +522 -30
  2. package/package.json +1 -1
package/dist/mage.js CHANGED
@@ -58250,7 +58250,7 @@ function applyMiddleware() {
58250
58250
 
58251
58251
  var thunk = createThunkMiddleware();
58252
58252
  thunk.withExtraArgument = createThunkMiddleware;var name = "mage-engine";
58253
- var version$1 = "3.24.3";
58253
+ var version$1 = "3.24.4";
58254
58254
  var description = "A WebGL Javascript Game Engine, built on top of THREE.js and many other libraries.";
58255
58255
  var main = "dist/mage.js";
58256
58256
  var author$1 = {
@@ -60666,7 +60666,6 @@ const extractMaterialProperty = (elementBody, property) => {
60666
60666
  return found;
60667
60667
  }
60668
60668
  };
60669
- const serialiseMaterial = material => omit(UNDESIRED_SERIALISED_MATERIAL_PROPERTIES, material === null || material === void 0 ? void 0 : material.toJSON());
60670
60669
  const processMaterial = (material, callback) => Array.isArray(material) ? material.map(callback) : callback(material);
60671
60670
  const replaceMaterialByName = (name, mesh, materialOptions) => {
60672
60671
  if (!hasMaterial(mesh)) return;
@@ -60906,7 +60905,16 @@ const tweenTo = function (origin, target) {
60906
60905
  const qz = Number(z) || 0;
60907
60906
  const qw = Number(w) || 1;
60908
60907
 
60909
- _this.getBody().quaternion.set(qx, qy, qz, qw);
60908
+ _this.getBody().quaternion.set(qx, qy, qz, qw); // Update matrix world and skeleton for skinned meshes
60909
+
60910
+
60911
+ _this.getBody().updateMatrixWorld(true);
60912
+
60913
+ _this.getBody().traverse(child => {
60914
+ if (child.isSkinnedMesh && child.skeleton) {
60915
+ child.skeleton.update();
60916
+ }
60917
+ });
60910
60918
  });
60911
60919
 
60912
60920
  _defineProperty$1(_assertThisInitialized(_this), "setUuid", uuid => {
@@ -61525,7 +61533,9 @@ const tweenTo = function (origin, target) {
61525
61533
  const sx = Number(scale.x) || 1;
61526
61534
  const sy = Number(scale.y) || 1;
61527
61535
  const sz = Number(scale.z) || 1;
61528
- this.body.scale.set(sx, sy, sz);
61536
+ this.body.scale.set(sx, sy, sz); // Update matrix world for all children including skinned meshes
61537
+
61538
+ this.getBody().updateMatrixWorld(true);
61529
61539
  }
61530
61540
  }
61531
61541
  }, {
@@ -61559,7 +61569,17 @@ const tweenTo = function (origin, target) {
61559
61569
  const px = Number(position.x) || 0;
61560
61570
  const py = Number(position.y) || 0;
61561
61571
  const pz = Number(position.z) || 0;
61562
- this.getBody().position.set(px, py, pz);
61572
+ this.getBody().position.set(px, py, pz); // Update matrix world to ensure skinned mesh skeletons follow the parent transform
61573
+
61574
+ this.getBody().updateMatrixWorld(true); // For skinned meshes, we need to rebind the skeleton after position change
61575
+ // The bindMatrix captures where the mesh was when bound, so we need to update it
61576
+
61577
+ this.getBody().traverse(child => {
61578
+ if (child.isSkinnedMesh && child.skeleton) {
61579
+ // Rebind with current matrix to update the bind pose
61580
+ child.bind(child.skeleton, child.matrixWorld);
61581
+ }
61582
+ });
61563
61583
  }
61564
61584
  }
61565
61585
  }, {
@@ -61586,7 +61606,9 @@ const tweenTo = function (origin, target) {
61586
61606
  const rx = Number(rotation.x) || 0;
61587
61607
  const ry = Number(rotation.y) || 0;
61588
61608
  const rz = Number(rotation.z) || 0;
61589
- this.getBody().rotation.set(rx, ry, rz);
61609
+ this.getBody().rotation.set(rx, ry, rz); // Update matrix world for all children including skinned meshes
61610
+
61611
+ this.getBody().updateMatrixWorld(true);
61590
61612
  }
61591
61613
  }
61592
61614
  }, {
@@ -61609,7 +61631,14 @@ const tweenTo = function (origin, target) {
61609
61631
  quaternion
61610
61632
  } = worldTransform;
61611
61633
  this.getBody().setWorldPosition(position);
61612
- this.getBody().setWorldQuaternion(quaternion);
61634
+ this.getBody().setWorldQuaternion(quaternion); // Update matrix world and skeleton for skinned meshes
61635
+
61636
+ this.getBody().updateMatrixWorld(true);
61637
+ this.getBody().traverse(child => {
61638
+ if (child.isSkinnedMesh && child.skeleton) {
61639
+ child.skeleton.update();
61640
+ }
61641
+ });
61613
61642
  }
61614
61643
  }, {
61615
61644
  key: "translate",
@@ -61623,7 +61652,9 @@ const tweenTo = function (origin, target) {
61623
61652
  if (this.hasBody()) {
61624
61653
  this.body.translateX(x);
61625
61654
  this.body.translateY(y);
61626
- this.body.translateZ(z);
61655
+ this.body.translateZ(z); // Update matrix world for all children including skinned meshes
61656
+
61657
+ this.getBody().updateMatrixWorld(true);
61627
61658
  }
61628
61659
  }
61629
61660
  }, {
@@ -61850,7 +61881,9 @@ const tweenTo = function (origin, target) {
61850
61881
  }]);
61851
61882
 
61852
61883
  return Entity;
61853
- }(EventDispatcher);let AnimationHandler = /*#__PURE__*/function (_EventDispatcher) {
61884
+ }(EventDispatcher);const DEFAULT_BLEND_DURATION = 0.3;
61885
+
61886
+ let AnimationHandler = /*#__PURE__*/function (_EventDispatcher) {
61854
61887
  _inherits(AnimationHandler, _EventDispatcher);
61855
61888
 
61856
61889
  var _super = _createSuper(AnimationHandler);
@@ -61880,6 +61913,8 @@ const tweenTo = function (origin, target) {
61880
61913
  _this.mixer = new AnimationMixer(mesh);
61881
61914
  _this.animations = animations;
61882
61915
  _this.isPlaying = false;
61916
+ _this.currentAction = null;
61917
+ _this.activeActions = new Map(); // Track multiple active actions for layered blending
61883
61918
 
61884
61919
  _this.addEventsListeners();
61885
61920
 
@@ -61930,39 +61965,260 @@ const tweenTo = function (origin, target) {
61930
61965
 
61931
61966
  this.isPlaying = false;
61932
61967
  }
61968
+ /**
61969
+ * Stop a specific animation by name/index with optional fade out
61970
+ * @param {string|number} id - Animation name or index
61971
+ * @param {number} fadeOutDuration - Duration to fade out (0 for immediate stop)
61972
+ */
61973
+
61974
+ }, {
61975
+ key: "stopAnimation",
61976
+ value: function stopAnimation(id) {
61977
+ let fadeOutDuration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
61978
+ const clip = this.getAction(id);
61979
+ if (!clip) return;
61980
+ const action = this.mixer.clipAction(clip);
61981
+
61982
+ if (fadeOutDuration > 0) {
61983
+ action.fadeOut(fadeOutDuration);
61984
+ } else {
61985
+ action.stop();
61986
+ }
61987
+
61988
+ this.activeActions.delete(id);
61989
+
61990
+ if (this.currentAction === action) {
61991
+ this.currentAction = null;
61992
+ this.isPlaying = false;
61993
+ }
61994
+ }
61995
+ /**
61996
+ * Play an animation with optional blending from current animation
61997
+ * @param {string|number} id - Animation name or index
61998
+ * @param {Object} options - Playback options
61999
+ * @param {number} options.loop - Loop mode (LoopRepeat, LoopOnce, etc.)
62000
+ * @param {number} options.blendDuration - Duration to blend from previous animation
62001
+ * @param {number} options.timeScale - Playback speed (1 = normal, 2 = double speed)
62002
+ * @param {number} options.weight - Animation weight for layered blending (0-1)
62003
+ * @param {boolean} options.clampWhenFinished - Keep last frame when finished (for LoopOnce)
62004
+ */
62005
+
61933
62006
  }, {
61934
62007
  key: "playAnimation",
61935
- value: function playAnimation(id, options) {
61936
- const action = this.getAction(id);
62008
+ value: function playAnimation(id) {
62009
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
62010
+ const clip = this.getAction(id);
62011
+
62012
+ if (!clip) {
62013
+ console.warn(ANIMATION_NOT_FOUND);
62014
+ return null;
62015
+ }
62016
+
61937
62017
  const {
61938
- loop = LoopRepeat
62018
+ loop = LoopRepeat,
62019
+ blendDuration = DEFAULT_BLEND_DURATION,
62020
+ timeScale = 1,
62021
+ weight = 1,
62022
+ clampWhenFinished = true
61939
62023
  } = options;
61940
62024
  this.isPlaying = true;
61941
62025
 
61942
62026
  if (this.currentAction) {
61943
- this.fadeToAnimation(action, options);
61944
- } else if (action) {
61945
- this.currentAction = this.mixer.clipAction(action).setLoop(loop).play();
62027
+ return this.crossFadeTo(id, { ...options,
62028
+ blendDuration
62029
+ });
61946
62030
  } else {
62031
+ const action = this.mixer.clipAction(clip);
62032
+ this.currentAction = action;
62033
+ this.activeActions.set(id, action);
62034
+ action.reset().setLoop(loop).setEffectiveTimeScale(timeScale).setEffectiveWeight(weight);
62035
+
62036
+ if (loop === LoopOnce) {
62037
+ action.clampWhenFinished = clampWhenFinished;
62038
+ }
62039
+
62040
+ action.play();
62041
+ return action;
62042
+ }
62043
+ }
62044
+ /**
62045
+ * Crossfade from current animation to a new animation
62046
+ * @param {string|number} id - Target animation name or index
62047
+ * @param {Object} options - Blend options
62048
+ * @param {number} options.blendDuration - Duration of the crossfade
62049
+ * @param {number} options.loop - Loop mode for the new animation
62050
+ * @param {number} options.timeScale - Playback speed
62051
+ * @param {boolean} options.warp - Whether to warp time scales during blend
62052
+ */
62053
+
62054
+ }, {
62055
+ key: "crossFadeTo",
62056
+ value: function crossFadeTo(id) {
62057
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
62058
+ const clip = this.getAction(id);
62059
+
62060
+ if (!clip) {
61947
62061
  console.warn(ANIMATION_NOT_FOUND);
62062
+ return null;
62063
+ }
62064
+
62065
+ const {
62066
+ blendDuration = DEFAULT_BLEND_DURATION,
62067
+ loop = LoopRepeat,
62068
+ timeScale = 1,
62069
+ warp = false,
62070
+ clampWhenFinished = true
62071
+ } = options;
62072
+ const previousAction = this.currentAction;
62073
+ const newAction = this.mixer.clipAction(clip);
62074
+
62075
+ if (previousAction === newAction) {
62076
+ return newAction; // Already playing this animation
62077
+ }
62078
+
62079
+ this.currentAction = newAction;
62080
+ this.activeActions.set(id, newAction);
62081
+ newAction.reset().setLoop(loop).setEffectiveTimeScale(timeScale).setEffectiveWeight(1);
62082
+
62083
+ if (loop === LoopOnce) {
62084
+ newAction.clampWhenFinished = clampWhenFinished;
62085
+ }
62086
+
62087
+ newAction.play();
62088
+
62089
+ if (previousAction) {
62090
+ if (warp) {
62091
+ // Synchronize time scales during crossfade
62092
+ previousAction.crossFadeTo(newAction, blendDuration, true);
62093
+ } else {
62094
+ // Standard crossfade
62095
+ previousAction.fadeOut(blendDuration);
62096
+ newAction.fadeIn(blendDuration);
62097
+ }
61948
62098
  }
62099
+
62100
+ return newAction;
61949
62101
  }
62102
+ /**
62103
+ * Legacy method - kept for backwards compatibility
62104
+ */
62105
+
61950
62106
  }, {
61951
62107
  key: "fadeToAnimation",
61952
62108
  value: function fadeToAnimation(action) {
61953
62109
  let {
61954
- duration = 0.2,
62110
+ duration = DEFAULT_BLEND_DURATION,
61955
62111
  loop = LoopRepeat
61956
62112
  } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
61957
62113
  const previousAction = this.currentAction;
61958
62114
  this.currentAction = this.mixer.clipAction(action);
61959
62115
 
61960
- if (previousAction !== this.currentAction) {
62116
+ if (previousAction && previousAction !== this.currentAction) {
61961
62117
  previousAction.fadeOut(duration);
61962
62118
  }
61963
62119
 
61964
62120
  this.currentAction.reset().setEffectiveTimeScale(1).setEffectiveWeight(1).fadeIn(duration).setLoop(loop).play();
61965
62121
  }
62122
+ /**
62123
+ * Set the weight of an animation for layered blending
62124
+ * @param {string|number} id - Animation name or index
62125
+ * @param {number} weight - Weight value (0-1)
62126
+ * @param {number} fadeDuration - Optional fade duration to reach target weight
62127
+ */
62128
+
62129
+ }, {
62130
+ key: "setAnimationWeight",
62131
+ value: function setAnimationWeight(id, weight) {
62132
+ let fadeDuration = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
62133
+ const clip = this.getAction(id);
62134
+ if (!clip) return;
62135
+ const action = this.mixer.clipAction(clip);
62136
+
62137
+ if (fadeDuration > 0) {
62138
+ // Gradually change weight
62139
+ const startWeight = action.getEffectiveWeight();
62140
+ const startTime = this.mixer.time;
62141
+
62142
+ const updateWeight = () => {
62143
+ const elapsed = this.mixer.time - startTime;
62144
+ const t = Math.min(elapsed / fadeDuration, 1);
62145
+ action.setEffectiveWeight(startWeight + (weight - startWeight) * t);
62146
+ }; // This will be called in the update loop
62147
+
62148
+
62149
+ action._weightUpdateFn = updateWeight;
62150
+ } else {
62151
+ action.setEffectiveWeight(weight);
62152
+ }
62153
+ }
62154
+ /**
62155
+ * Set the time scale (speed) of an animation
62156
+ * @param {string|number} id - Animation name or index
62157
+ * @param {number} timeScale - Speed multiplier (1 = normal)
62158
+ */
62159
+
62160
+ }, {
62161
+ key: "setAnimationTimeScale",
62162
+ value: function setAnimationTimeScale(id, timeScale) {
62163
+ const clip = this.getAction(id);
62164
+ if (!clip) return;
62165
+ const action = this.mixer.clipAction(clip);
62166
+ action.setEffectiveTimeScale(timeScale);
62167
+ }
62168
+ /**
62169
+ * Get the current time of an animation
62170
+ * @param {string|number} id - Animation name or index
62171
+ * @returns {number} Current time in seconds
62172
+ */
62173
+
62174
+ }, {
62175
+ key: "getAnimationTime",
62176
+ value: function getAnimationTime(id) {
62177
+ const clip = this.getAction(id);
62178
+ if (!clip) return 0;
62179
+ const action = this.mixer.clipAction(clip);
62180
+ return action.time;
62181
+ }
62182
+ /**
62183
+ * Set the current time of an animation
62184
+ * @param {string|number} id - Animation name or index
62185
+ * @param {number} time - Time in seconds
62186
+ */
62187
+
62188
+ }, {
62189
+ key: "setAnimationTime",
62190
+ value: function setAnimationTime(id, time) {
62191
+ const clip = this.getAction(id);
62192
+ if (!clip) return;
62193
+ const action = this.mixer.clipAction(clip);
62194
+ action.time = time;
62195
+ }
62196
+ /**
62197
+ * Check if a specific animation is currently playing
62198
+ * @param {string|number} id - Animation name or index
62199
+ * @returns {boolean}
62200
+ */
62201
+
62202
+ }, {
62203
+ key: "isAnimationPlaying",
62204
+ value: function isAnimationPlaying(id) {
62205
+ const clip = this.getAction(id);
62206
+ if (!clip) return false;
62207
+ const action = this.mixer.clipAction(clip);
62208
+ return action.isRunning();
62209
+ }
62210
+ /**
62211
+ * Get the duration of an animation clip
62212
+ * @param {string|number} id - Animation name or index
62213
+ * @returns {number} Duration in seconds
62214
+ */
62215
+
62216
+ }, {
62217
+ key: "getAnimationDuration",
62218
+ value: function getAnimationDuration(id) {
62219
+ const clip = this.getAction(id);
62220
+ return clip ? clip.duration : 0;
62221
+ }
61966
62222
  }, {
61967
62223
  key: "update",
61968
62224
  value: function update(dt) {
@@ -62407,8 +62663,8 @@ let Element$1 = /*#__PURE__*/function (_Entity) {
62407
62663
  }
62408
62664
  }
62409
62665
  }, {
62410
- key: "stopAnimation",
62411
- value: function stopAnimation() {
62666
+ key: "stopCurrentAnimation",
62667
+ value: function stopCurrentAnimation() {
62412
62668
  if (!this.hasAnimations()) return;
62413
62669
 
62414
62670
  if (this.hasAnimationHandler()) {
@@ -62430,6 +62686,123 @@ let Element$1 = /*#__PURE__*/function (_Entity) {
62430
62686
 
62431
62687
  return [];
62432
62688
  }
62689
+ /**
62690
+ * Crossfade from current animation to a new animation
62691
+ * @param {string|number} id - Target animation name or index
62692
+ * @param {Object} options - Blend options
62693
+ * @param {number} options.blendDuration - Duration of the crossfade (default: 0.3)
62694
+ * @param {number} options.loop - Loop mode (LoopRepeat, LoopOnce)
62695
+ * @param {number} options.timeScale - Playback speed (1 = normal)
62696
+ * @param {boolean} options.warp - Whether to warp time scales during blend
62697
+ */
62698
+
62699
+ }, {
62700
+ key: "crossFadeTo",
62701
+ value: function crossFadeTo(id) {
62702
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
62703
+ if (!this.hasAnimations()) return;
62704
+
62705
+ if (this.hasAnimationHandler()) {
62706
+ return this.animationHandler.crossFadeTo(id, options);
62707
+ } else {
62708
+ console.warn(ANIMATION_HANDLER_NOT_FOUND);
62709
+ }
62710
+ }
62711
+ /**
62712
+ * Stop an animation. If no id is provided, stops the current animation.
62713
+ * @param {string|number} [id] - Animation name or index (optional)
62714
+ * @param {number} fadeOutDuration - Duration to fade out (0 for immediate)
62715
+ */
62716
+
62717
+ }, {
62718
+ key: "stopAnimation",
62719
+ value: function stopAnimation(id) {
62720
+ let fadeOutDuration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
62721
+ if (!this.hasAnimations()) return;
62722
+
62723
+ if (this.hasAnimationHandler()) {
62724
+ // If no id provided, stop the current animation
62725
+ if (id === undefined || id === null) {
62726
+ this.animationHandler.stopCurrentAnimation();
62727
+ } else {
62728
+ this.animationHandler.stopAnimation(id, fadeOutDuration);
62729
+ }
62730
+ } else {
62731
+ console.warn(ANIMATION_HANDLER_NOT_FOUND);
62732
+ }
62733
+ }
62734
+ /**
62735
+ * Set the weight of an animation for layered blending
62736
+ * @param {string|number} id - Animation name or index
62737
+ * @param {number} weight - Weight value (0-1)
62738
+ * @param {number} fadeDuration - Optional fade duration
62739
+ */
62740
+
62741
+ }, {
62742
+ key: "setAnimationWeight",
62743
+ value: function setAnimationWeight(id, weight) {
62744
+ let fadeDuration = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
62745
+ if (!this.hasAnimations()) return;
62746
+
62747
+ if (this.hasAnimationHandler()) {
62748
+ this.animationHandler.setAnimationWeight(id, weight, fadeDuration);
62749
+ } else {
62750
+ console.warn(ANIMATION_HANDLER_NOT_FOUND);
62751
+ }
62752
+ }
62753
+ /**
62754
+ * Set the playback speed of an animation
62755
+ * @param {string|number} id - Animation name or index
62756
+ * @param {number} timeScale - Speed multiplier (1 = normal, 2 = double speed)
62757
+ */
62758
+
62759
+ }, {
62760
+ key: "setAnimationTimeScale",
62761
+ value: function setAnimationTimeScale(id, timeScale) {
62762
+ if (!this.hasAnimations()) return;
62763
+
62764
+ if (this.hasAnimationHandler()) {
62765
+ this.animationHandler.setAnimationTimeScale(id, timeScale);
62766
+ } else {
62767
+ console.warn(ANIMATION_HANDLER_NOT_FOUND);
62768
+ }
62769
+ }
62770
+ /**
62771
+ * Get the duration of an animation clip
62772
+ * @param {string|number} id - Animation name or index
62773
+ * @returns {number} Duration in seconds
62774
+ */
62775
+
62776
+ }, {
62777
+ key: "getAnimationDuration",
62778
+ value: function getAnimationDuration(id) {
62779
+ if (!this.hasAnimations()) return 0;
62780
+
62781
+ if (this.hasAnimationHandler()) {
62782
+ return this.animationHandler.getAnimationDuration(id);
62783
+ } else {
62784
+ console.warn(ANIMATION_HANDLER_NOT_FOUND);
62785
+ return 0;
62786
+ }
62787
+ }
62788
+ /**
62789
+ * Check if a specific animation is currently playing
62790
+ * @param {string|number} id - Animation name or index
62791
+ * @returns {boolean}
62792
+ */
62793
+
62794
+ }, {
62795
+ key: "isAnimationPlaying",
62796
+ value: function isAnimationPlaying(id) {
62797
+ if (!this.hasAnimations()) return false;
62798
+
62799
+ if (this.hasAnimationHandler()) {
62800
+ return this.animationHandler.isAnimationPlaying(id);
62801
+ } else {
62802
+ console.warn(ANIMATION_HANDLER_NOT_FOUND);
62803
+ return false;
62804
+ }
62805
+ }
62433
62806
  /**
62434
62807
  * TODO: the entire physics system needs to be a component
62435
62808
  * e.g.
@@ -63161,6 +63534,50 @@ let Element$1 = /*#__PURE__*/function (_Entity) {
63161
63534
 
63162
63535
  Physics$1.disposeElement(this);
63163
63536
  }
63537
+ /**
63538
+ * Serialize only the material properties that the Importer actually uses.
63539
+ * This avoids bloating the JSON with full Three.js material data.
63540
+ */
63541
+
63542
+ }, {
63543
+ key: "serializeMaterialProperties",
63544
+ value: function serializeMaterialProperties() {
63545
+ const materialType = this.getMaterialType(); // Use allowed properties for known material types, or fallback to STANDARD properties
63546
+ // which has the most comprehensive set
63547
+
63548
+ const allowedProperties = MATERIAL_PROPERTIES_MAP[materialType] || MATERIAL_PROPERTIES_MAP[MATERIALS.STANDARD] || [];
63549
+ const materials = this.getMaterials();
63550
+ if (!materials.length) return [];
63551
+ return materials.map(material => {
63552
+ const serialized = {
63553
+ // Always include the material type for the inspector
63554
+ type: material.type
63555
+ };
63556
+
63557
+ for (const prop of allowedProperties) {
63558
+ if (material[prop] !== undefined) {
63559
+ const value = material[prop]; // Serialize Color objects to plain objects
63560
+
63561
+ if (value && value.isColor) {
63562
+ serialized[prop] = {
63563
+ r: value.r,
63564
+ g: value.g,
63565
+ b: value.b
63566
+ };
63567
+ } else if (value && value.isVector2) {
63568
+ serialized[prop] = {
63569
+ x: value.x,
63570
+ y: value.y
63571
+ };
63572
+ } else {
63573
+ serialized[prop] = value;
63574
+ }
63575
+ }
63576
+ }
63577
+
63578
+ return serialized;
63579
+ });
63580
+ }
63164
63581
  }, {
63165
63582
  key: "toJSON",
63166
63583
  value: function toJSON() {
@@ -63169,18 +63586,26 @@ let Element$1 = /*#__PURE__*/function (_Entity) {
63169
63586
  if (this.isSerializable()) {
63170
63587
  const color = this.getColor();
63171
63588
  return { ..._get(_getPrototypeOf(Element.prototype), "toJSON", this).call(this, parseJSON),
63589
+ // Physics options (state is not used by Importer, only options)
63172
63590
  physics: {
63173
- state: serializeMap(this.getPhysicsState()),
63174
63591
  options: this.getPhysicsOptions()
63175
63592
  },
63176
- // body: this.body.toJSON(),
63593
+ // Textures with serialized map
63177
63594
  textures: serializeMap(this.textures),
63595
+ // Material type name (e.g., "STANDARD", "BASIC")
63178
63596
  materialType: this.getMaterialType(),
63179
- materials: this.getMaterials().map(serialiseMaterial),
63180
- // no need to have geometry, for basic entities we can build from the type
63181
- // models have a reference to the model itself
63597
+ // Lightweight material properties (type + allowed properties only)
63598
+ materials: this.serializeMaterialProperties(),
63599
+ // Opacity
63182
63600
  opacity: this.opacity,
63183
- color: parseJSON ? serializeColor(color) : color
63601
+ // Color (for inspector display)
63602
+ color: parseJSON ? {
63603
+ r: color === null || color === void 0 ? void 0 : color.r,
63604
+ g: color === null || color === void 0 ? void 0 : color.g,
63605
+ b: color === null || color === void 0 ? void 0 : color.b
63606
+ } : color,
63607
+ // Animation names (for inspector display - actual animations loaded from model file)
63608
+ animations: this.getAvailableAnimations()
63184
63609
  };
63185
63610
  }
63186
63611
  }
@@ -81237,6 +81662,10 @@ const glbParser = gltf => {
81237
81662
  if (object.isMesh) {
81238
81663
  object.castShadow = true;
81239
81664
  }
81665
+
81666
+ if (object.isSkinnedMesh) {
81667
+ object.frustumCulled = false;
81668
+ }
81240
81669
  });
81241
81670
  return {
81242
81671
  animations,
@@ -81256,6 +81685,11 @@ const gltfParser = gltf => {
81256
81685
  return null;
81257
81686
  }
81258
81687
 
81688
+ gltf.scene.traverse(node => {
81689
+ if (node.isSkinnedMesh) {
81690
+ node.frustumCulled = false;
81691
+ }
81692
+ });
81259
81693
  return {
81260
81694
  scene: gltf.scene,
81261
81695
  animations: gltf.animations
@@ -81326,6 +81760,7 @@ const fbxParser = scene => {
81326
81760
 
81327
81761
  scene.traverse(node => {
81328
81762
  if (node.isSkinnedMesh) {
81763
+ node.frustumCulled = false;
81329
81764
  processMaterial(node.material, material => material.skinning = true);
81330
81765
  }
81331
81766
  });
@@ -81383,6 +81818,8 @@ let Models = /*#__PURE__*/function (_EventDispatcher) {
81383
81818
  });
81384
81819
 
81385
81820
  _defineProperty$1(_assertThisInitialized(_this), "create", function (name) {
81821
+ var _scene$children, _scene$traverse, _element$getBody;
81822
+
81386
81823
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
81387
81824
  const builtAssetId = buildAssetId(name, _this.currentLevel);
81388
81825
  const modelData = _this.map[name] || _this.map[builtAssetId];
@@ -81396,7 +81833,23 @@ let Models = /*#__PURE__*/function (_EventDispatcher) {
81396
81833
  scene,
81397
81834
  animations,
81398
81835
  extension
81399
- } = modelData; // Validate that scene is a valid THREE.js object with required methods
81836
+ } = modelData; // Debug: Log model structure
81837
+
81838
+ console.log(`[Mage] Creating model "${name}":`, {
81839
+ hasScene: !!scene,
81840
+ sceneType: scene === null || scene === void 0 ? void 0 : scene.type,
81841
+ childrenCount: scene === null || scene === void 0 ? void 0 : (_scene$children = scene.children) === null || _scene$children === void 0 ? void 0 : _scene$children.length,
81842
+ animations: (animations === null || animations === void 0 ? void 0 : animations.length) || 0,
81843
+ extension
81844
+ }); // Debug: Check for meshes in the scene
81845
+
81846
+ let meshCount = 0;
81847
+ let skinnedMeshCount = 0;
81848
+ scene === null || scene === void 0 ? void 0 : (_scene$traverse = scene.traverse) === null || _scene$traverse === void 0 ? void 0 : _scene$traverse.call(scene, node => {
81849
+ if (node.isMesh) meshCount++;
81850
+ if (node.isSkinnedMesh) skinnedMeshCount++;
81851
+ });
81852
+ console.log(`[Mage] Model "${name}" contains: ${meshCount} meshes, ${skinnedMeshCount} skinned meshes`); // Validate that scene is a valid THREE.js object with required methods
81400
81853
 
81401
81854
  if (!scene || typeof scene.clone !== 'function' || typeof scene.traverse !== 'function') {
81402
81855
  console.warn(`[Mage] Model "${name}" has invalid scene object. Got:`, Object.keys(modelData));
@@ -81408,8 +81861,17 @@ let Models = /*#__PURE__*/function (_EventDispatcher) {
81408
81861
  builtAssetId,
81409
81862
  ...options
81410
81863
  };
81411
- let model;
81412
- const useSkeletonClone = extension !== EXTENSIONS.COLLADA && hasAnimations(animations);
81864
+ let model; // Check if the scene contains any skinned meshes
81865
+
81866
+ let hasSkinnedMeshes = false;
81867
+ scene.traverse(node => {
81868
+ if (node.isSkinnedMesh) {
81869
+ hasSkinnedMeshes = true;
81870
+ }
81871
+ }); // Use SkeletonUtils.clone for models with skinned meshes OR animations
81872
+ // Regular clone() doesn't properly handle skeleton binding
81873
+
81874
+ const useSkeletonClone = extension !== EXTENSIONS.COLLADA && (hasAnimations(animations) || hasSkinnedMeshes);
81413
81875
 
81414
81876
  try {
81415
81877
  if (useSkeletonClone) {
@@ -81440,6 +81902,21 @@ let Models = /*#__PURE__*/function (_EventDispatcher) {
81440
81902
 
81441
81903
  if (hasAnimations(animations)) {
81442
81904
  element.addAnimationHandler(animations);
81905
+ } // Debug: Log element body info
81906
+
81907
+
81908
+ const body = (_element$getBody = element.getBody) === null || _element$getBody === void 0 ? void 0 : _element$getBody.call(element);
81909
+
81910
+ if (body) {
81911
+ var _body$position, _body$position$toArra, _body$scale, _body$scale$toArray, _body$children;
81912
+
81913
+ console.log(`[Mage] Element "${name}" body:`, {
81914
+ type: body.type,
81915
+ visible: body.visible,
81916
+ position: (_body$position = body.position) === null || _body$position === void 0 ? void 0 : (_body$position$toArra = _body$position.toArray) === null || _body$position$toArra === void 0 ? void 0 : _body$position$toArra.call(_body$position),
81917
+ scale: (_body$scale = body.scale) === null || _body$scale === void 0 ? void 0 : (_body$scale$toArray = _body$scale.toArray) === null || _body$scale$toArray === void 0 ? void 0 : _body$scale$toArray.call(_body$scale),
81918
+ childrenCount: (_body$children = body.children) === null || _body$children === void 0 ? void 0 : _body$children.length
81919
+ });
81443
81920
  }
81444
81921
 
81445
81922
  return element;
@@ -90964,7 +91441,10 @@ var Particles$1 = new Particles();let Orbit = /*#__PURE__*/function (_EventDispa
90964
91441
  object.quaternion.copy(_this._tempQuaternion.setFromAxisAngle(_this.rotationAxis, _this.rotationAngle));
90965
91442
  object.quaternion.multiply(_this._quaternionStart);
90966
91443
  }
90967
- }
91444
+ } // Update skinned mesh skeletons after transform
91445
+
91446
+
91447
+ _this.updateSkinnedMeshSkeletons();
90968
91448
 
90969
91449
  _this.dispatchEvent(_this.changeEvent);
90970
91450
 
@@ -91247,7 +91727,19 @@ var Particles$1 = new Particles();let Orbit = /*#__PURE__*/function (_EventDispa
91247
91727
  value: function attach(element) {
91248
91728
  if (!element) return;
91249
91729
  this.object = element.getBody();
91250
- this.visible = true;
91730
+ this.visible = true; // Force immediate matrix world update to ensure gizmo position is correct
91731
+ // This is especially important for skinned meshes where the matrix may not be current
91732
+
91733
+ if (this.object) {
91734
+ this.object.updateMatrixWorld(true);
91735
+ }
91736
+ } // Update matrix world for skinned meshes after transform changes
91737
+
91738
+ }, {
91739
+ key: "updateSkinnedMeshSkeletons",
91740
+ value: function updateSkinnedMeshSkeletons() {
91741
+ if (!this.object) return;
91742
+ this.object.updateMatrixWorld(true);
91251
91743
  }
91252
91744
  }, {
91253
91745
  key: "detach",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mage-engine",
3
- "version": "3.24.3",
3
+ "version": "3.24.4",
4
4
  "description": "A WebGL Javascript Game Engine, built on top of THREE.js and many other libraries.",
5
5
  "main": "dist/mage.js",
6
6
  "author": {