minerender 2.1.0-alpha.130 → 2.1.0-alpha.132

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.
@@ -2691,13 +2691,13 @@ function applyElementRotation(rotation, geometry) {
2691
2691
  break;
2692
2692
  }
2693
2693
  if (rotation.rescale) {
2694
- if (Math.abs(rotation.angle) === 22.5) {
2695
- const s = 1 / Math.cos(0.39269909262657166);
2696
- geometry.scale(s, 1, s);
2697
- } else {
2698
- const s = 1 / Math.cos(0.7853981852531433);
2699
- geometry.scale(s, 1, s);
2700
- }
2694
+ const angle = toRadians(rotation.angle);
2695
+ const s = 1 / Math.max(Math.abs(Math.cos(angle)), Math.abs(Math.sin(angle)));
2696
+ geometry.scale(
2697
+ rotation.axis === "x" /* X */ ? 1 : s,
2698
+ rotation.axis === "y" /* Y */ ? 1 : s,
2699
+ rotation.axis === "z" /* Z */ ? 1 : s
2700
+ );
2701
2701
  }
2702
2702
  geometry.translate(origin.x, origin.y, origin.z);
2703
2703
  }
@@ -5003,6 +5003,13 @@ var init_EntityAnimation = __esm({
5003
5003
 
5004
5004
  // src/entity/scene/EntityAnimationPlayer.ts
5005
5005
  import { Euler as Euler3, Vector3 as Vector35 } from "three";
5006
+ function validateAnimationWeights(weights, count) {
5007
+ const values = Array.from(weights);
5008
+ if (values.length !== count || values.some((weight) => !Number.isFinite(weight) || weight < 0)) {
5009
+ throw new Error("Animation weights must match the clip count and contain only finite nonnegative numbers");
5010
+ }
5011
+ return values;
5012
+ }
5006
5013
  var ZERO, EntityAnimationPlayer;
5007
5014
  var init_EntityAnimationPlayer = __esm({
5008
5015
  "src/entity/scene/EntityAnimationPlayer.ts"() {
@@ -5011,12 +5018,20 @@ var init_EntityAnimationPlayer = __esm({
5011
5018
  EntityAnimationPlayer = class {
5012
5019
  constructor() {
5013
5020
  this.parts = [];
5021
+ this._animations = [];
5022
+ this._weights = [];
5023
+ this.loops = [];
5014
5024
  this._time = 0;
5015
- this.loop = false;
5016
5025
  this.speed = 1;
5017
5026
  }
5018
5027
  get animation() {
5019
- return this._animation;
5028
+ return this._animations[0];
5029
+ }
5030
+ get animations() {
5031
+ return this._animations.slice();
5032
+ }
5033
+ get weights() {
5034
+ return this._weights.slice();
5020
5035
  }
5021
5036
  get time() {
5022
5037
  return this._time;
@@ -5026,10 +5041,20 @@ var init_EntityAnimationPlayer = __esm({
5026
5041
  * without them, the current pose is used. Stopping restores the pose from before playback.
5027
5042
  */
5028
5043
  play(layers, animation, options = {}, poses) {
5044
+ this.playAnimations(layers, [animation], options, poses);
5045
+ }
5046
+ /**
5047
+ * Starts clips on matching parts with a shared clock. Weighted offsets add to the supplied baked poses
5048
+ * or, without poses, to each animated part's pose when playback starts.
5049
+ */
5050
+ playAnimations(layers, animations, options = {}, poses) {
5051
+ const weights = validateAnimationWeights(options.weights ?? animations.map(() => 1), animations.length);
5029
5052
  this.stop();
5030
- this._animation = animation;
5053
+ this._animations = animations.slice();
5054
+ this._weights = weights;
5055
+ if (!animations.length) return;
5031
5056
  this._time = options.time ?? 0;
5032
- this.loop = options.loop ?? animation.loop;
5057
+ this.loops = animations.map((animation) => options.loop ?? animation.loop);
5033
5058
  this.speed = options.speed ?? 1;
5034
5059
  for (const layer of layers) {
5035
5060
  const objects = [];
@@ -5038,7 +5063,8 @@ var init_EntityAnimationPlayer = __esm({
5038
5063
  if (poses.has(object4)) objects.push(object4);
5039
5064
  });
5040
5065
  } else {
5041
- for (const bone of Object.keys(animation.bones)) {
5066
+ const bones = new Set(animations.flatMap((animation) => Object.keys(animation.bones)));
5067
+ for (const bone of bones) {
5042
5068
  const object4 = layer.getObjectByName(`group:${bone}`);
5043
5069
  if (object4) objects.push(object4);
5044
5070
  }
@@ -5062,7 +5088,7 @@ var init_EntityAnimationPlayer = __esm({
5062
5088
  }
5063
5089
  /** Restores the poses from before playback. Returns whether an animation was active. */
5064
5090
  stop() {
5065
- if (!this._animation) return false;
5091
+ if (!this._animations.length) return false;
5066
5092
  for (const part of this.parts) {
5067
5093
  part.object.position.copy(part.position);
5068
5094
  part.object.rotation.copy(part.rotation);
@@ -5074,12 +5100,14 @@ var init_EntityAnimationPlayer = __esm({
5074
5100
  /** Forgets the animation without touching the parts, for parts that no longer exist. */
5075
5101
  clear() {
5076
5102
  this.parts = [];
5077
- this._animation = void 0;
5103
+ this._animations = [];
5104
+ this._weights = [];
5105
+ this.loops = [];
5078
5106
  this._time = 0;
5079
5107
  }
5080
5108
  /** Applies the pose at an absolute playback time. Returns `false` when no clip is active. */
5081
5109
  setTime(seconds) {
5082
- if (!this._animation) return false;
5110
+ if (!this._animations.length) return false;
5083
5111
  this._time = seconds;
5084
5112
  this.apply();
5085
5113
  return true;
@@ -5088,8 +5116,30 @@ var init_EntityAnimationPlayer = __esm({
5088
5116
  advance(deltaSeconds) {
5089
5117
  return this.setTime(this._time + deltaSeconds * this.speed);
5090
5118
  }
5119
+ /** Changes weights in clip order and reapplies the current time without restarting playback. */
5120
+ setWeights(weights) {
5121
+ if (!this._animations.length) return false;
5122
+ const validated = validateAnimationWeights(weights, this._animations.length);
5123
+ this._weights = validated;
5124
+ this.apply();
5125
+ return true;
5126
+ }
5091
5127
  apply() {
5092
- const pose = sampleEntityAnimation(this._animation, this._time, this.loop);
5128
+ const pose = /* @__PURE__ */ Object.create(null);
5129
+ for (let i = 0; i < this._animations.length; i++) {
5130
+ const weight = this._weights[i];
5131
+ if (weight === 0) continue;
5132
+ const sampled = sampleEntityAnimation(this._animations[i], this._time, this.loops[i]);
5133
+ for (const [bone, channels] of Object.entries(sampled)) {
5134
+ const blended = pose[bone] ??= {};
5135
+ for (const channel of ENTITY_ANIMATION_CHANNELS) {
5136
+ const value = channels[channel];
5137
+ if (!value) continue;
5138
+ const target = blended[channel] ??= [0, 0, 0];
5139
+ for (let axis = 0; axis < 3; axis++) target[axis] += value[axis] * weight;
5140
+ }
5141
+ }
5142
+ }
5093
5143
  for (const part of this.parts) {
5094
5144
  const { position = ZERO, rotation = ZERO, scale = ZERO } = pose[part.bone] ?? {};
5095
5145
  part.object.position.set(part.basePosition.x + position[0], part.basePosition.y + position[1], part.basePosition.z + position[2]);
@@ -5713,7 +5763,7 @@ var init_EntityObject = __esm({
5713
5763
  for (const geometry of this.geometries) geometry.dispose();
5714
5764
  this.geometries.clear();
5715
5765
  for (const dispose of this.disposeWireframes.splice(0)) dispose();
5716
- for (const player of this.animationPlayers.splice(0)) player.clear();
5766
+ for (const { player } of this.animationPlayers.splice(0)) player.clear();
5717
5767
  this.partPoses.clear();
5718
5768
  super.disposeAndRemoveAllChildren();
5719
5769
  }
@@ -5748,15 +5798,31 @@ var init_EntityObject = __esm({
5748
5798
  //<editor-fold desc="ANIMATION">
5749
5799
  /** The first active clip, until playback is stopped or the object is disposed. */
5750
5800
  get animation() {
5751
- return this.animationPlayers[0]?.animation;
5801
+ return this.animationPlayers[0]?.player.animation;
5752
5802
  }
5753
- /** Clips playing together, each targeting a separate geometry layer. */
5803
+ /** Clips playing together, in the order passed to {@link playAnimations}. */
5754
5804
  get activeAnimations() {
5755
- return this.animationPlayers.map((player) => player.animation);
5805
+ const animations = [];
5806
+ for (const { player, indices } of this.animationPlayers) {
5807
+ player.animations.forEach((animation, i) => {
5808
+ animations[indices[i]] = animation;
5809
+ });
5810
+ }
5811
+ return animations;
5812
+ }
5813
+ /** Weights of the active clips, in the same order as {@link activeAnimations}. */
5814
+ get animationWeights() {
5815
+ const weights = [];
5816
+ for (const { player, indices } of this.animationPlayers) {
5817
+ player.weights.forEach((weight, i) => {
5818
+ weights[indices[i]] = weight;
5819
+ });
5820
+ }
5821
+ return weights;
5756
5822
  }
5757
5823
  /** Seconds since the animation started, before looping. */
5758
5824
  get animationTime() {
5759
- return this.animationPlayers[0]?.time ?? 0;
5825
+ return this.animationPlayers[0]?.player.time ?? 0;
5760
5826
  }
5761
5827
  /**
5762
5828
  * Plays a clip from {@link Entities.getAnimations}, replacing all active clips.
@@ -5767,42 +5833,59 @@ var init_EntityObject = __esm({
5767
5833
  playAnimation(animation, options) {
5768
5834
  this.playAnimations([animation], options);
5769
5835
  }
5770
- /** Plays clips with a shared clock and no blending. Selected layers without their own clip follow `main`. */
5836
+ /**
5837
+ * Plays clips with a shared clock, adding their weighted offsets within each geometry layer.
5838
+ * Weights default to 1 and are not normalized. Selected layers without their own clip follow `main`.
5839
+ */
5771
5840
  playAnimations(animations, options) {
5772
- const targets = /* @__PURE__ */ new Set();
5773
- const selections = animations.map((animation) => {
5841
+ const weights = validateAnimationWeights(options?.weights ?? animations.map(() => 1), animations.length);
5842
+ const selections = /* @__PURE__ */ new Map();
5843
+ animations.forEach((animation, index) => {
5774
5844
  const layer = animation.layer ?? "main";
5775
- if (targets.has(layer)) throw new Error(`Multiple animations target layer "${layer}"`);
5776
- targets.add(layer);
5777
5845
  const names = Object.keys(this.entityLayers).filter((name) => name.split("#")[0] === layer);
5778
5846
  if (!names.length) throw new Error(`Entity ${this.entity.id} has no selected animation layer "${layer}"`);
5779
- return { animation, names };
5847
+ let selection = selections.get(layer);
5848
+ if (!selection) selections.set(layer, selection = { animations: [], names, indices: [] });
5849
+ selection.animations.push(animation);
5850
+ selection.indices.push(index);
5780
5851
  });
5781
- const main = selections.find(({ animation }) => (animation.layer ?? "main") === "main");
5782
- if (main) main.names.push(...Object.keys(this.entityLayers).filter((name) => !targets.has(name.split("#")[0])));
5852
+ const main = selections.get("main");
5853
+ if (main) main.names.push(...Object.keys(this.entityLayers).filter((name) => !selections.has(name.split("#")[0])));
5783
5854
  this.createMeshes();
5784
5855
  this.stopAnimation();
5785
- for (const { animation, names } of selections) {
5856
+ for (const { animations: clips, names, indices } of selections.values()) {
5786
5857
  const player = new EntityAnimationPlayer();
5787
- player.play(names.map((name) => this.getLayerGroup(name)), animation, options, this.partPoses);
5788
- this.animationPlayers.push(player);
5858
+ player.playAnimations(
5859
+ names.map((name) => this.getLayerGroup(name)),
5860
+ clips,
5861
+ { ...options, weights: indices.map((index) => weights[index]) },
5862
+ this.partPoses
5863
+ );
5864
+ this.animationPlayers.push({ player, indices });
5789
5865
  }
5790
5866
  this.notifyDirty();
5791
5867
  }
5868
+ /** Changes weights in active clip order, notifying the scene without restarting the shared clock. Has no effect without an animation. */
5869
+ setAnimationWeights(weights) {
5870
+ if (!this.animationPlayers.length) return;
5871
+ const values = validateAnimationWeights(weights, this.activeAnimations.length);
5872
+ for (const { player, indices } of this.animationPlayers) player.setWeights(indices.map((index) => values[index]));
5873
+ this.notifyDirty();
5874
+ }
5792
5875
  /** Stops all clips and restores the poses from before playback. */
5793
5876
  stopAnimation() {
5794
5877
  const players = this.animationPlayers.splice(0);
5795
- for (const player of players) player.stop();
5878
+ for (const { player } of players) player.stop();
5796
5879
  if (players.length) this.notifyDirty();
5797
5880
  }
5798
5881
  /** Poses the object at an explicit time in seconds. Has no effect without an animation. */
5799
5882
  setAnimationTime(seconds) {
5800
- for (const player of this.animationPlayers) player.setTime(seconds);
5883
+ for (const { player } of this.animationPlayers) player.setTime(seconds);
5801
5884
  if (this.animationPlayers.length) this.notifyDirty();
5802
5885
  }
5803
5886
  /** Advances the animation by a frame delta in seconds, scaled by its speed. Has no effect without an animation. */
5804
5887
  advanceAnimation(deltaSeconds) {
5805
- for (const player of this.animationPlayers) player.advance(deltaSeconds);
5888
+ for (const { player } of this.animationPlayers) player.advance(deltaSeconds);
5806
5889
  if (this.animationPlayers.length) this.notifyDirty();
5807
5890
  }
5808
5891
  //</editor-fold>
@@ -20523,6 +20606,7 @@ export {
20523
20606
  toRadians,
20524
20607
  toTrianglesDrawMode,
20525
20608
  trimCanvas,
20609
+ validateAnimationWeights,
20526
20610
  yieldToEventLoop
20527
20611
  };
20528
20612
  //# sourceMappingURL=index.mjs.map