minerender 2.1.0-alpha.131 → 2.1.0-alpha.133

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.
@@ -309,6 +309,9 @@ var init_Caching = __esm({
309
309
  static {
310
310
  this.blockEntityIndexCache = _Caching.createAssetCache();
311
311
  }
312
+ static {
313
+ this.unihexCache = _Caching.createAssetCache();
314
+ }
312
315
  static {
313
316
  this.fontCache = _Caching.createAssetCache();
314
317
  }
@@ -351,6 +354,7 @@ var init_Caching = __esm({
351
354
  this.entityAnimationCache,
352
355
  this.blockEntityIndexCache,
353
356
  this.fontCache,
357
+ this.unihexCache,
354
358
  this.bannerPatternCache,
355
359
  this.mineRenderDataCache,
356
360
  this.usernameUuidCache,
@@ -1408,6 +1412,7 @@ var init_AssetParsers = __esm({
1408
1412
  AssetParser2["IMAGE"] = "image";
1409
1413
  AssetParser2["LIST"] = "list";
1410
1414
  AssetParser2["JSON"] = "json";
1415
+ AssetParser2["BINARY"] = "binary";
1411
1416
  return AssetParser2;
1412
1417
  })(AssetParser || {});
1413
1418
  }
@@ -2878,7 +2883,7 @@ var init_parser = __esm({
2878
2883
  });
2879
2884
 
2880
2885
  // src/assets/source/HostedAssetSource.ts
2881
- var p3, DEFAULT_OPTIONS, HostedAssetSource;
2886
+ var BINARY_TIMEOUT, p3, DEFAULT_OPTIONS, HostedAssetSource;
2882
2887
  var init_HostedAssetSource = __esm({
2883
2888
  "src/assets/source/HostedAssetSource.ts"() {
2884
2889
  init_AssetSource();
@@ -2891,6 +2896,7 @@ var init_HostedAssetSource = __esm({
2891
2896
  init_AssetLoader();
2892
2897
  init_parser();
2893
2898
  init_merge();
2899
+ BINARY_TIMEOUT = 6e4;
2894
2900
  p3 = prefix("HostedAssetSource");
2895
2901
  DEFAULT_OPTIONS = {
2896
2902
  retryDefaults: true
@@ -2961,6 +2967,17 @@ var init_HostedAssetSource = __esm({
2961
2967
  }
2962
2968
  };
2963
2969
  }
2970
+ static {
2971
+ this.BINARY = {
2972
+ config(request) {
2973
+ request.timeout = BINARY_TIMEOUT;
2974
+ request.responseType = "arraybuffer";
2975
+ },
2976
+ parse(response) {
2977
+ return { data: new Uint8Array(response.data) };
2978
+ }
2979
+ };
2980
+ }
2964
2981
  static {
2965
2982
  this.PARSER_MAP = /* @__PURE__ */ new Map([
2966
2983
  ["model" /* MODEL */, _HostedAssetSource.MODEL],
@@ -2969,7 +2986,8 @@ var init_HostedAssetSource = __esm({
2969
2986
  ["nbt" /* NBT */, _HostedAssetSource.NBT],
2970
2987
  ["image" /* IMAGE */, _HostedAssetSource.IMAGE],
2971
2988
  ["list" /* LIST */, _HostedAssetSource.LIST],
2972
- ["json" /* JSON */, _HostedAssetSource.JSON]
2989
+ ["json" /* JSON */, _HostedAssetSource.JSON],
2990
+ ["binary" /* BINARY */, _HostedAssetSource.BINARY]
2973
2991
  ]);
2974
2992
  }
2975
2993
  /** Retries missing assets with the `minecraft` namespace and default root. Other failures reject. */
@@ -4855,6 +4873,178 @@ var init_Entities = __esm({
4855
4873
  }
4856
4874
  });
4857
4875
 
4876
+ // src/assets/_fonts/Unihex.ts
4877
+ var Unihex_exports = {};
4878
+ __export(Unihex_exports, {
4879
+ loadUnihex: () => loadUnihex
4880
+ });
4881
+ import { TextWriter, Uint8ArrayReader, ZipReader as ZipReader2 } from "@zip.js/zip.js";
4882
+ function readHex(text2, filename, glyphs) {
4883
+ const lines = text2.split("\n");
4884
+ if (lines[lines.length - 1] === "") lines.pop();
4885
+ lines.forEach((line, index) => {
4886
+ const match = /^([0-9A-F]{4,6}):([0-9A-F]+)$/.exec(line);
4887
+ if (!match || ![32, 64, 96, 128].includes(match[2].length)) {
4888
+ throw new Error(`Invalid Unihex record in ${filename} at line ${index + 1}`);
4889
+ }
4890
+ const codepoint = parseInt(match[1], 16);
4891
+ if (codepoint > 1114111) throw new Error(`Invalid Unihex codepoint in ${filename} at line ${index + 1}`);
4892
+ const bitWidth = match[2].length / 4;
4893
+ const rowLength = bitWidth / 4;
4894
+ const rows = new Uint32Array(GLYPH_HEIGHT);
4895
+ let mask = 0;
4896
+ for (let y = 0; y < GLYPH_HEIGHT; y++) {
4897
+ rows[y] = parseInt(match[2].slice(y * rowLength, (y + 1) * rowLength), 16);
4898
+ mask |= rows[y];
4899
+ }
4900
+ let left = 0, right = bitWidth;
4901
+ if (mask) {
4902
+ while (!(mask >>> bitWidth - left - 1 & 1)) left++;
4903
+ right = bitWidth - 1;
4904
+ while (!(mask >>> bitWidth - right - 1 & 1)) right--;
4905
+ }
4906
+ glyphs.set(codepoint, { rows, bitWidth, left, right });
4907
+ });
4908
+ }
4909
+ async function parseUnihex(key) {
4910
+ try {
4911
+ const asset2 = await AssetLoader.get(key, "binary" /* BINARY */);
4912
+ if (!asset2) throw new Error(`Could not load Unihex archive ${key.toNamespacedString()}`);
4913
+ const records = /* @__PURE__ */ new Map();
4914
+ const reader = new ZipReader2(new Uint8ArrayReader(asset2.data), { useWebWorkers: false, useCompressionStream: false });
4915
+ try {
4916
+ for (const entry of await reader.getEntries()) {
4917
+ if (!entry.directory && entry.filename.endsWith(".hex")) {
4918
+ readHex(await entry.getData(new TextWriter(), { checkSignature: true }), entry.filename, records);
4919
+ }
4920
+ }
4921
+ } finally {
4922
+ await reader.close();
4923
+ }
4924
+ const indices = new Int32Array(1114112).fill(-1);
4925
+ const rows = new Uint32Array(records.size * GLYPH_HEIGHT), metrics = new Uint8Array(records.size * 3);
4926
+ let index = 0;
4927
+ for (const [codepoint, record] of records) {
4928
+ indices[codepoint] = index;
4929
+ rows.set(record.rows, index * GLYPH_HEIGHT);
4930
+ metrics.set([record.bitWidth, record.left, record.right], index * 3);
4931
+ index++;
4932
+ }
4933
+ return { indices, rows, metrics };
4934
+ } catch (error) {
4935
+ console.warn(`Could not load Unihex file ${key.toNamespacedString()}`, error);
4936
+ return void 0;
4937
+ }
4938
+ }
4939
+ async function loadUnihex(provider, fontKey, characters) {
4940
+ const origin = new AssetKey(DEFAULT_NAMESPACE, "", void 0, void 0, "assets", "", fontKey.root);
4941
+ const key = AssetKey.parse("font", provider.hex_file, origin);
4942
+ key.assetType = void 0;
4943
+ const glyphs = /* @__PURE__ */ new Map();
4944
+ let overrides;
4945
+ try {
4946
+ overrides = (provider.size_overrides ?? []).map((override) => {
4947
+ const from = override.from.codePointAt(0), to = override.to.codePointAt(0);
4948
+ if ([...override.from].length !== 1 || [...override.to].length !== 1 || from >= to || !Number.isInteger(override.left) || !Number.isInteger(override.right) || override.right < override.left) {
4949
+ throw new Error(`Invalid Unihex size override in font ${fontKey.toNamespacedString()}`);
4950
+ }
4951
+ return { ...override, from, to };
4952
+ });
4953
+ } catch (error) {
4954
+ console.warn(`Could not load Unihex file ${key.toNamespacedString()}`, error);
4955
+ return glyphs;
4956
+ }
4957
+ const file = await Caching.unihexCache.get(key.serialize(), () => parseUnihex(key));
4958
+ if (!file) return glyphs;
4959
+ let atlas = atlases.get(provider);
4960
+ if (!atlas) {
4961
+ atlas = new UnihexAtlas();
4962
+ atlases.set(provider, atlas);
4963
+ }
4964
+ for (const character of characters) {
4965
+ const codepoint = character.codePointAt(0), index = file.indices[codepoint];
4966
+ if (index < 0) continue;
4967
+ const record = {
4968
+ rows: file.rows.subarray(index * GLYPH_HEIGHT, (index + 1) * GLYPH_HEIGHT),
4969
+ bitWidth: file.metrics[index * 3],
4970
+ left: file.metrics[index * 3 + 1],
4971
+ right: file.metrics[index * 3 + 2]
4972
+ };
4973
+ const override = overrides.find((range) => codepoint >= range.from && codepoint <= range.to);
4974
+ if (override) {
4975
+ record.left = override.left;
4976
+ record.right = override.right;
4977
+ }
4978
+ const width = record.right - record.left + 1;
4979
+ let rendered;
4980
+ const render = () => rendered ??= atlas.draw(record);
4981
+ glyphs.set(character, {
4982
+ get image() {
4983
+ return render().image;
4984
+ },
4985
+ get x() {
4986
+ return render().x;
4987
+ },
4988
+ get y() {
4989
+ return render().y;
4990
+ },
4991
+ width,
4992
+ height: GLYPH_HEIGHT,
4993
+ scale: 0.5,
4994
+ ascent: 7,
4995
+ advance: Math.floor(width / 2) + 1,
4996
+ boldOffset: 0.5,
4997
+ shadowOffset: 0.5
4998
+ });
4999
+ }
5000
+ return glyphs;
5001
+ }
5002
+ var ATLAS_SIZE, GLYPH_HEIGHT, UnihexAtlas, atlases;
5003
+ var init_Unihex = __esm({
5004
+ "src/assets/_fonts/Unihex.ts"() {
5005
+ init_AssetKey();
5006
+ init_AssetLoader();
5007
+ init_Assets();
5008
+ init_Caching();
5009
+ init_AssetParsers();
5010
+ init_CanvasCompat();
5011
+ ATLAS_SIZE = 512;
5012
+ GLYPH_HEIGHT = 16;
5013
+ UnihexAtlas = class {
5014
+ constructor() {
5015
+ this.x = 0;
5016
+ this.y = 0;
5017
+ }
5018
+ draw(glyph) {
5019
+ const width = glyph.right - glyph.left + 1;
5020
+ if (this.image && this.x + width > this.image.width) {
5021
+ this.x = 0;
5022
+ this.y += GLYPH_HEIGHT;
5023
+ }
5024
+ if (!this.image || this.y + GLYPH_HEIGHT > this.image.height || width > this.image.width) {
5025
+ this.image = createCanvas2(Math.max(ATLAS_SIZE, width), ATLAS_SIZE);
5026
+ this.x = 0;
5027
+ this.y = 0;
5028
+ }
5029
+ const context = this.image.getContext("2d");
5030
+ const pixels = context.createImageData(width, GLYPH_HEIGHT);
5031
+ for (let y = 0; y < GLYPH_HEIGHT; y++) {
5032
+ for (let x = 0; x < width; x++) {
5033
+ const column = glyph.left + x;
5034
+ if (column < 0 || column >= glyph.bitWidth || !(glyph.rows[y] >>> glyph.bitWidth - column - 1 & 1)) continue;
5035
+ pixels.data.fill(255, (y * width + x) * 4, (y * width + x + 1) * 4);
5036
+ }
5037
+ }
5038
+ const result = { image: this.image, x: this.x, y: this.y };
5039
+ context.putImageData(pixels, this.x, this.y);
5040
+ this.x += width;
5041
+ return result;
5042
+ }
5043
+ };
5044
+ atlases = /* @__PURE__ */ new WeakMap();
5045
+ }
5046
+ });
5047
+
4858
5048
  // src/assets/Fonts.ts
4859
5049
  var Fonts;
4860
5050
  var init_Fonts = __esm({
@@ -4865,7 +5055,7 @@ var init_Fonts = __esm({
4865
5055
  init_ModelTextures();
4866
5056
  init_Caching();
4867
5057
  Fonts = class {
4868
- /** Combines font definitions in source-priority order. Unsupported Unihex and TrueType providers are skipped. */
5058
+ /** Combines font definitions in source-priority order. TrueType providers are skipped. */
4869
5059
  static async get(key = "minecraft:default") {
4870
5060
  const fontKey = typeof key === "string" ? AssetKey.parse("font", key) : key;
4871
5061
  return await Caching.fontCache.get(fontKey.serialize(), () => this.load(fontKey, []));
@@ -4881,6 +5071,7 @@ var init_Fonts = __esm({
4881
5071
  const stack = await AssetLoader.getAll(key, "json" /* JSON */);
4882
5072
  if (stack.length === 0) throw new Error(`Could not load font ${key.toNamespacedString()}`);
4883
5073
  const glyphs = /* @__PURE__ */ new Map();
5074
+ const providers = [];
4884
5075
  for (const asset2 of stack) {
4885
5076
  for (const provider of asset2.providers) {
4886
5077
  if (provider.filter?.uniform || provider.filter?.jp) continue;
@@ -4899,17 +5090,43 @@ var init_Fonts = __esm({
4899
5090
  break;
4900
5091
  case "reference":
4901
5092
  case "minecraft:reference":
4902
- provided = (await this.load(this.relatedKey("font", provider.id, key), [...parents, serialized])).glyphs;
5093
+ const reference = await this.load(this.relatedKey("font", provider.id, key), [...parents, serialized]);
5094
+ if (reference.resolve) {
5095
+ providers.push(async (characters) => {
5096
+ await reference.resolve(characters);
5097
+ return reference.glyphs;
5098
+ });
5099
+ continue;
5100
+ }
5101
+ provided = reference.glyphs;
4903
5102
  break;
5103
+ case "unihex":
5104
+ case "minecraft:unihex":
5105
+ providers.push(async (characters) => (await Promise.resolve().then(() => (init_Unihex(), Unihex_exports))).loadUnihex(provider, key, characters));
5106
+ continue;
4904
5107
  default:
4905
5108
  continue;
4906
5109
  }
4907
- for (const [character, glyph] of provided) {
5110
+ if (providers.every((provider2) => provider2 instanceof Map)) for (const [character, glyph] of provided) {
4908
5111
  if (character !== "\0" && !glyphs.has(character)) glyphs.set(character, glyph);
4909
5112
  }
5113
+ providers.push(provided);
5114
+ }
5115
+ }
5116
+ return { glyphs, resolve: providers.every((provider) => provider instanceof Map) ? void 0 : async (characters) => {
5117
+ const remaining = new Set([...characters].filter((character) => character !== "\0" && character !== "\n" && character !== "\r" && !glyphs.has(character)));
5118
+ for (const provider of providers) {
5119
+ if (!remaining.size) break;
5120
+ const provided = provider instanceof Map ? provider : await provider([...remaining].join(""));
5121
+ for (const character of remaining) {
5122
+ const glyph = provided.get(character);
5123
+ if (glyph) {
5124
+ glyphs.set(character, glyph);
5125
+ remaining.delete(character);
5126
+ }
5127
+ }
4910
5128
  }
4911
- }
4912
- return { glyphs };
5129
+ } };
4913
5130
  }
4914
5131
  static async bitmap(provider, fontKey) {
4915
5132
  const rows = provider.chars.map((row) => [...row]);
@@ -5003,6 +5220,13 @@ var init_EntityAnimation = __esm({
5003
5220
 
5004
5221
  // src/entity/scene/EntityAnimationPlayer.ts
5005
5222
  import { Euler as Euler3, Vector3 as Vector35 } from "three";
5223
+ function validateAnimationWeights(weights, count) {
5224
+ const values = Array.from(weights);
5225
+ if (values.length !== count || values.some((weight) => !Number.isFinite(weight) || weight < 0)) {
5226
+ throw new Error("Animation weights must match the clip count and contain only finite nonnegative numbers");
5227
+ }
5228
+ return values;
5229
+ }
5006
5230
  var ZERO, EntityAnimationPlayer;
5007
5231
  var init_EntityAnimationPlayer = __esm({
5008
5232
  "src/entity/scene/EntityAnimationPlayer.ts"() {
@@ -5011,12 +5235,20 @@ var init_EntityAnimationPlayer = __esm({
5011
5235
  EntityAnimationPlayer = class {
5012
5236
  constructor() {
5013
5237
  this.parts = [];
5238
+ this._animations = [];
5239
+ this._weights = [];
5240
+ this.loops = [];
5014
5241
  this._time = 0;
5015
- this.loop = false;
5016
5242
  this.speed = 1;
5017
5243
  }
5018
5244
  get animation() {
5019
- return this._animation;
5245
+ return this._animations[0];
5246
+ }
5247
+ get animations() {
5248
+ return this._animations.slice();
5249
+ }
5250
+ get weights() {
5251
+ return this._weights.slice();
5020
5252
  }
5021
5253
  get time() {
5022
5254
  return this._time;
@@ -5026,10 +5258,20 @@ var init_EntityAnimationPlayer = __esm({
5026
5258
  * without them, the current pose is used. Stopping restores the pose from before playback.
5027
5259
  */
5028
5260
  play(layers, animation, options = {}, poses) {
5261
+ this.playAnimations(layers, [animation], options, poses);
5262
+ }
5263
+ /**
5264
+ * Starts clips on matching parts with a shared clock. Weighted offsets add to the supplied baked poses
5265
+ * or, without poses, to each animated part's pose when playback starts.
5266
+ */
5267
+ playAnimations(layers, animations, options = {}, poses) {
5268
+ const weights = validateAnimationWeights(options.weights ?? animations.map(() => 1), animations.length);
5029
5269
  this.stop();
5030
- this._animation = animation;
5270
+ this._animations = animations.slice();
5271
+ this._weights = weights;
5272
+ if (!animations.length) return;
5031
5273
  this._time = options.time ?? 0;
5032
- this.loop = options.loop ?? animation.loop;
5274
+ this.loops = animations.map((animation) => options.loop ?? animation.loop);
5033
5275
  this.speed = options.speed ?? 1;
5034
5276
  for (const layer of layers) {
5035
5277
  const objects = [];
@@ -5038,7 +5280,8 @@ var init_EntityAnimationPlayer = __esm({
5038
5280
  if (poses.has(object4)) objects.push(object4);
5039
5281
  });
5040
5282
  } else {
5041
- for (const bone of Object.keys(animation.bones)) {
5283
+ const bones = new Set(animations.flatMap((animation) => Object.keys(animation.bones)));
5284
+ for (const bone of bones) {
5042
5285
  const object4 = layer.getObjectByName(`group:${bone}`);
5043
5286
  if (object4) objects.push(object4);
5044
5287
  }
@@ -5062,7 +5305,7 @@ var init_EntityAnimationPlayer = __esm({
5062
5305
  }
5063
5306
  /** Restores the poses from before playback. Returns whether an animation was active. */
5064
5307
  stop() {
5065
- if (!this._animation) return false;
5308
+ if (!this._animations.length) return false;
5066
5309
  for (const part of this.parts) {
5067
5310
  part.object.position.copy(part.position);
5068
5311
  part.object.rotation.copy(part.rotation);
@@ -5074,12 +5317,14 @@ var init_EntityAnimationPlayer = __esm({
5074
5317
  /** Forgets the animation without touching the parts, for parts that no longer exist. */
5075
5318
  clear() {
5076
5319
  this.parts = [];
5077
- this._animation = void 0;
5320
+ this._animations = [];
5321
+ this._weights = [];
5322
+ this.loops = [];
5078
5323
  this._time = 0;
5079
5324
  }
5080
5325
  /** Applies the pose at an absolute playback time. Returns `false` when no clip is active. */
5081
5326
  setTime(seconds) {
5082
- if (!this._animation) return false;
5327
+ if (!this._animations.length) return false;
5083
5328
  this._time = seconds;
5084
5329
  this.apply();
5085
5330
  return true;
@@ -5088,8 +5333,30 @@ var init_EntityAnimationPlayer = __esm({
5088
5333
  advance(deltaSeconds) {
5089
5334
  return this.setTime(this._time + deltaSeconds * this.speed);
5090
5335
  }
5336
+ /** Changes weights in clip order and reapplies the current time without restarting playback. */
5337
+ setWeights(weights) {
5338
+ if (!this._animations.length) return false;
5339
+ const validated = validateAnimationWeights(weights, this._animations.length);
5340
+ this._weights = validated;
5341
+ this.apply();
5342
+ return true;
5343
+ }
5091
5344
  apply() {
5092
- const pose = sampleEntityAnimation(this._animation, this._time, this.loop);
5345
+ const pose = /* @__PURE__ */ Object.create(null);
5346
+ for (let i = 0; i < this._animations.length; i++) {
5347
+ const weight = this._weights[i];
5348
+ if (weight === 0) continue;
5349
+ const sampled = sampleEntityAnimation(this._animations[i], this._time, this.loops[i]);
5350
+ for (const [bone, channels] of Object.entries(sampled)) {
5351
+ const blended = pose[bone] ??= {};
5352
+ for (const channel of ENTITY_ANIMATION_CHANNELS) {
5353
+ const value = channels[channel];
5354
+ if (!value) continue;
5355
+ const target = blended[channel] ??= [0, 0, 0];
5356
+ for (let axis = 0; axis < 3; axis++) target[axis] += value[axis] * weight;
5357
+ }
5358
+ }
5359
+ }
5093
5360
  for (const part of this.parts) {
5094
5361
  const { position = ZERO, rotation = ZERO, scale = ZERO } = pose[part.bone] ?? {};
5095
5362
  part.object.position.set(part.basePosition.x + position[0], part.basePosition.y + position[1], part.basePosition.z + position[2]);
@@ -5713,7 +5980,7 @@ var init_EntityObject = __esm({
5713
5980
  for (const geometry of this.geometries) geometry.dispose();
5714
5981
  this.geometries.clear();
5715
5982
  for (const dispose of this.disposeWireframes.splice(0)) dispose();
5716
- for (const player of this.animationPlayers.splice(0)) player.clear();
5983
+ for (const { player } of this.animationPlayers.splice(0)) player.clear();
5717
5984
  this.partPoses.clear();
5718
5985
  super.disposeAndRemoveAllChildren();
5719
5986
  }
@@ -5748,15 +6015,31 @@ var init_EntityObject = __esm({
5748
6015
  //<editor-fold desc="ANIMATION">
5749
6016
  /** The first active clip, until playback is stopped or the object is disposed. */
5750
6017
  get animation() {
5751
- return this.animationPlayers[0]?.animation;
6018
+ return this.animationPlayers[0]?.player.animation;
5752
6019
  }
5753
- /** Clips playing together, each targeting a separate geometry layer. */
6020
+ /** Clips playing together, in the order passed to {@link playAnimations}. */
5754
6021
  get activeAnimations() {
5755
- return this.animationPlayers.map((player) => player.animation);
6022
+ const animations = [];
6023
+ for (const { player, indices } of this.animationPlayers) {
6024
+ player.animations.forEach((animation, i) => {
6025
+ animations[indices[i]] = animation;
6026
+ });
6027
+ }
6028
+ return animations;
6029
+ }
6030
+ /** Weights of the active clips, in the same order as {@link activeAnimations}. */
6031
+ get animationWeights() {
6032
+ const weights = [];
6033
+ for (const { player, indices } of this.animationPlayers) {
6034
+ player.weights.forEach((weight, i) => {
6035
+ weights[indices[i]] = weight;
6036
+ });
6037
+ }
6038
+ return weights;
5756
6039
  }
5757
6040
  /** Seconds since the animation started, before looping. */
5758
6041
  get animationTime() {
5759
- return this.animationPlayers[0]?.time ?? 0;
6042
+ return this.animationPlayers[0]?.player.time ?? 0;
5760
6043
  }
5761
6044
  /**
5762
6045
  * Plays a clip from {@link Entities.getAnimations}, replacing all active clips.
@@ -5767,42 +6050,59 @@ var init_EntityObject = __esm({
5767
6050
  playAnimation(animation, options) {
5768
6051
  this.playAnimations([animation], options);
5769
6052
  }
5770
- /** Plays clips with a shared clock and no blending. Selected layers without their own clip follow `main`. */
6053
+ /**
6054
+ * Plays clips with a shared clock, adding their weighted offsets within each geometry layer.
6055
+ * Weights default to 1 and are not normalized. Selected layers without their own clip follow `main`.
6056
+ */
5771
6057
  playAnimations(animations, options) {
5772
- const targets = /* @__PURE__ */ new Set();
5773
- const selections = animations.map((animation) => {
6058
+ const weights = validateAnimationWeights(options?.weights ?? animations.map(() => 1), animations.length);
6059
+ const selections = /* @__PURE__ */ new Map();
6060
+ animations.forEach((animation, index) => {
5774
6061
  const layer = animation.layer ?? "main";
5775
- if (targets.has(layer)) throw new Error(`Multiple animations target layer "${layer}"`);
5776
- targets.add(layer);
5777
6062
  const names = Object.keys(this.entityLayers).filter((name) => name.split("#")[0] === layer);
5778
6063
  if (!names.length) throw new Error(`Entity ${this.entity.id} has no selected animation layer "${layer}"`);
5779
- return { animation, names };
6064
+ let selection = selections.get(layer);
6065
+ if (!selection) selections.set(layer, selection = { animations: [], names, indices: [] });
6066
+ selection.animations.push(animation);
6067
+ selection.indices.push(index);
5780
6068
  });
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])));
6069
+ const main = selections.get("main");
6070
+ if (main) main.names.push(...Object.keys(this.entityLayers).filter((name) => !selections.has(name.split("#")[0])));
5783
6071
  this.createMeshes();
5784
6072
  this.stopAnimation();
5785
- for (const { animation, names } of selections) {
6073
+ for (const { animations: clips, names, indices } of selections.values()) {
5786
6074
  const player = new EntityAnimationPlayer();
5787
- player.play(names.map((name) => this.getLayerGroup(name)), animation, options, this.partPoses);
5788
- this.animationPlayers.push(player);
6075
+ player.playAnimations(
6076
+ names.map((name) => this.getLayerGroup(name)),
6077
+ clips,
6078
+ { ...options, weights: indices.map((index) => weights[index]) },
6079
+ this.partPoses
6080
+ );
6081
+ this.animationPlayers.push({ player, indices });
5789
6082
  }
5790
6083
  this.notifyDirty();
5791
6084
  }
6085
+ /** Changes weights in active clip order, notifying the scene without restarting the shared clock. Has no effect without an animation. */
6086
+ setAnimationWeights(weights) {
6087
+ if (!this.animationPlayers.length) return;
6088
+ const values = validateAnimationWeights(weights, this.activeAnimations.length);
6089
+ for (const { player, indices } of this.animationPlayers) player.setWeights(indices.map((index) => values[index]));
6090
+ this.notifyDirty();
6091
+ }
5792
6092
  /** Stops all clips and restores the poses from before playback. */
5793
6093
  stopAnimation() {
5794
6094
  const players = this.animationPlayers.splice(0);
5795
- for (const player of players) player.stop();
6095
+ for (const { player } of players) player.stop();
5796
6096
  if (players.length) this.notifyDirty();
5797
6097
  }
5798
6098
  /** Poses the object at an explicit time in seconds. Has no effect without an animation. */
5799
6099
  setAnimationTime(seconds) {
5800
- for (const player of this.animationPlayers) player.setTime(seconds);
6100
+ for (const { player } of this.animationPlayers) player.setTime(seconds);
5801
6101
  if (this.animationPlayers.length) this.notifyDirty();
5802
6102
  }
5803
6103
  /** Advances the animation by a frame delta in seconds, scaled by its speed. Has no effect without an animation. */
5804
6104
  advanceAnimation(deltaSeconds) {
5805
- for (const player of this.animationPlayers) player.advance(deltaSeconds);
6105
+ for (const { player } of this.animationPlayers) player.advance(deltaSeconds);
5806
6106
  if (this.animationPlayers.length) this.notifyDirty();
5807
6107
  }
5808
6108
  //</editor-fold>
@@ -8064,6 +8364,7 @@ async function layoutGuiText(text2, options = {}) {
8064
8364
  }
8065
8365
  const font = await Fonts.get(options.font);
8066
8366
  const runs = typeof text2 === "string" ? [{ text: text2 }] : text2;
8367
+ await font.resolve?.(runs.map((run) => run.text).join(""));
8067
8368
  const glyphs = [], lineWidths = [];
8068
8369
  let line = [], width = 0;
8069
8370
  let trailingNewline = false;
@@ -8090,7 +8391,7 @@ async function layoutGuiText(text2, options = {}) {
8090
8391
  continue;
8091
8392
  }
8092
8393
  const glyph = font.glyphs.get(character);
8093
- const advance = (glyph?.advance ?? 6) + (style.bold ? 1 : 0);
8394
+ const advance = (glyph?.advance ?? 6) + (style.bold ? glyph?.boldOffset ?? 1 : 0);
8094
8395
  line.push({ character, glyph, advance, style, x: 0, y: 0 });
8095
8396
  width += advance;
8096
8397
  while (width > maxWidth && line.length > 1) {
@@ -8118,7 +8419,7 @@ function createGuiTextGeometry(layout, shadow = true) {
8118
8419
  batches.push(batch);
8119
8420
  }
8120
8421
  const color2 = new Color8(isShadow ? (style.color & 16579836) >> 2 : style.color);
8121
- const offset = isShadow ? 1 : 0;
8422
+ const offset = isShadow ? glyph?.shadowOffset ?? 1 : 0;
8122
8423
  const top = glyph ? 7 - glyph.ascent : 0;
8123
8424
  const height = glyph ? glyph.height * glyph.scale : 8;
8124
8425
  const width = glyph ? glyph.width * glyph.scale : 5;
@@ -8148,7 +8449,7 @@ function createGuiTextGeometry(layout, shadow = true) {
8148
8449
  for (let i = 0; i < 4; i++) batch.colors.push(color2.r, color2.g, color2.b);
8149
8450
  batch.indices.push(base, base + 2, base + 1, base + 2, base + 3, base + 1);
8150
8451
  };
8151
- for (const boldOffset of style.bold ? [0, 1] : [0]) {
8452
+ for (const boldOffset of style.bold ? [0, glyph?.boldOffset ?? 1] : [0]) {
8152
8453
  if (glyph) quad(0, top, width, top + height, boldOffset);
8153
8454
  else {
8154
8455
  quad(0, 0, 5, 1, boldOffset);
@@ -18771,7 +19072,7 @@ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18771
19072
 
18772
19073
  // src/world/SectionGeometry.ts
18773
19074
  function buildSectionGeometry(input) {
18774
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion, biomeTints } = input;
19075
+ const { count, indices, templates, cullMasks, templateData, atlases: atlases2, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion, biomeTints } = input;
18775
19076
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18776
19077
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
18777
19078
  if (biomeTints && biomeTints.length !== count * 3) throw new RangeError("Section biome tints must contain RGB for each placement");
@@ -18806,8 +19107,8 @@ function buildSectionGeometry(input) {
18806
19107
  const locations = /* @__PURE__ */ new Map();
18807
19108
  for (const [layer, layerAtlases] of [...visibleAtlases].sort(([a], [b]) => a - b)) {
18808
19109
  const firstPage = pages.length;
18809
- for (const atlas of [...layerAtlases].sort((a, b) => Number(atlases[a].animated) - Number(atlases[b].animated) || (atlases[a].animated ? a - b : atlases[b].height - atlases[a].height))) {
18810
- const { width, height, animated } = atlases[atlas];
19110
+ for (const atlas of [...layerAtlases].sort((a, b) => Number(atlases2[a].animated) - Number(atlases2[b].animated) || (atlases2[a].animated ? a - b : atlases2[b].height - atlases2[a].height))) {
19111
+ const { width, height, animated } = atlases2[atlas];
18811
19112
  if (width > maxAtlasSize || height > maxAtlasSize) throw new RangeError("Model atlas exceeds the section atlas size limit");
18812
19113
  let selected = -1;
18813
19114
  for (let i = firstPage; !animated && i < pages.length; i++) {
@@ -18850,7 +19151,7 @@ function buildSectionGeometry(input) {
18850
19151
  });
18851
19152
  if (!quads.sprites.length) continue;
18852
19153
  if (!fluidPages.has(fluid)) {
18853
- const { width, height } = atlases[data.atlas];
19154
+ const { width, height } = atlases2[data.atlas];
18854
19155
  if (width > maxAtlasSize || height > maxAtlasSize) throw new RangeError("Model atlas exceeds the section atlas size limit");
18855
19156
  fluidPages.set(fluid, {
18856
19157
  layer: 2,
@@ -18868,7 +19169,7 @@ function buildSectionGeometry(input) {
18868
19169
  const tint = fluid === "water" && fluids.waterTints ? fluids.waterTints.subarray(tintIndex, tintIndex + 3) : data.tint;
18869
19170
  fluidPages.get(fluid).fluids.push({ quads, x, y, z, data, tint });
18870
19171
  }
18871
- pages.push(...[...fluidPages.values()].sort((a, b) => Number(atlases[a.placements[0].atlas].animated) - Number(atlases[b.placements[0].atlas].animated) || a.placements[0].atlas - b.placements[0].atlas));
19172
+ pages.push(...[...fluidPages.values()].sort((a, b) => Number(atlases2[a.placements[0].atlas].animated) - Number(atlases2[b.placements[0].atlas].animated) || a.placements[0].atlas - b.placements[0].atlas));
18872
19173
  }
18873
19174
  return pages.map((page) => {
18874
19175
  let faces = page.fluids.reduce((sum, fluid) => sum + fluid.quads.sprites.length, 0);
@@ -18885,7 +19186,7 @@ function buildSectionGeometry(input) {
18885
19186
  let vertex = 0, indexOffset = 0;
18886
19187
  for (const entry of page.entries) {
18887
19188
  const template = templateData[templates[entry]];
18888
- const atlas = atlases[template.atlas];
19189
+ const atlas = atlases2[template.atlas];
18889
19190
  const placement = locations.get(template.atlas);
18890
19191
  const mapU = (u) => (placement.x + u * atlas.width) / page.width;
18891
19192
  const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
@@ -18924,7 +19225,7 @@ function buildSectionGeometry(input) {
18924
19225
  }
18925
19226
  }
18926
19227
  for (const { quads, x, y, z, data, tint } of page.fluids) {
18927
- const atlas = atlases[data.atlas];
19228
+ const atlas = atlases2[data.atlas];
18928
19229
  const placement = page.placements[0];
18929
19230
  const mapU = (u) => (placement.x + u * atlas.width) / page.width;
18930
19231
  const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
@@ -19026,7 +19327,7 @@ var templateCache = /* @__PURE__ */ new WeakMap();
19026
19327
  var WATER_TINT = new Color12(4159204).toArray();
19027
19328
  function toInput(entries, maxAtlasSize, fluids, lighting) {
19028
19329
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
19029
- const atlases = [];
19330
+ const atlases2 = [];
19030
19331
  const atlasIds = /* @__PURE__ */ new Map();
19031
19332
  const templateIds = /* @__PURE__ */ new Map();
19032
19333
  const input = {
@@ -19049,9 +19350,9 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
19049
19350
  if (id === void 0) {
19050
19351
  let atlas = atlasIds.get(template.atlas);
19051
19352
  if (atlas === void 0) {
19052
- atlas = atlases.length;
19353
+ atlas = atlases2.length;
19053
19354
  atlasIds.set(template.atlas, atlas);
19054
- atlases.push(template.atlas);
19355
+ atlases2.push(template.atlas);
19055
19356
  input.atlases.push({ width: template.atlas.image.width, height: template.atlas.image.height, animated: template.atlas.hasAnimation });
19056
19357
  }
19057
19358
  let data = templateCache.get(template);
@@ -19110,9 +19411,9 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
19110
19411
  if (!atlas) continue;
19111
19412
  let id = atlasIds.get(atlas);
19112
19413
  if (id === void 0) {
19113
- id = atlases.length;
19414
+ id = atlases2.length;
19114
19415
  atlasIds.set(atlas, id);
19115
- atlases.push(atlas);
19416
+ atlases2.push(atlas);
19116
19417
  input.atlases.push({ width: atlas.image.width, height: atlas.image.height, animated: atlas.hasAnimation });
19117
19418
  }
19118
19419
  input.fluids[kind] = {
@@ -19123,7 +19424,7 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
19123
19424
  };
19124
19425
  }
19125
19426
  }
19126
- return { input, atlases };
19427
+ return { input, atlases: atlases2 };
19127
19428
  }
19128
19429
  var SectionMesh = class _SectionMesh extends Group3 {
19129
19430
  constructor() {
@@ -19138,23 +19439,23 @@ var SectionMesh = class _SectionMesh extends Group3 {
19138
19439
  }
19139
19440
  /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */
19140
19441
  static build(entries, maxAtlasSize = 2048, fluids, lighting) {
19141
- const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);
19142
- return this.fromPages(buildSectionGeometry(input), atlases);
19442
+ const { input, atlases: atlases2 } = toInput(entries, maxAtlasSize, fluids, lighting);
19443
+ return this.fromPages(buildSectionGeometry(input), atlases2);
19143
19444
  }
19144
19445
  /** Builds section-local meshes in a browser worker when available, with synchronous fallback. */
19145
19446
  static async buildAsync(entries, maxAtlasSize = 2048, fluids, lighting) {
19146
- const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);
19447
+ const { input, atlases: atlases2 } = toInput(entries, maxAtlasSize, fluids, lighting);
19147
19448
  const worker = SectionWorker.shared();
19148
19449
  const pages = worker ? await worker.build(input).catch(() => buildSectionGeometry(input)) : buildSectionGeometry(input);
19149
- return this.fromPages(pages, atlases);
19450
+ return this.fromPages(pages, atlases2);
19150
19451
  }
19151
- static fromPages(pages, atlases) {
19452
+ static fromPages(pages, atlases2) {
19152
19453
  const section = new _SectionMesh();
19153
19454
  for (const page of pages) {
19154
19455
  const canvas = createCanvas2(page.width, page.height);
19155
19456
  const context = canvas.getContext("2d");
19156
19457
  for (const { atlas, x, y } of page.placements) {
19157
- context.drawImage(atlases[atlas].image.canvas, x, y);
19458
+ context.drawImage(atlases2[atlas].image.canvas, x, y);
19158
19459
  }
19159
19460
  const geometry = new BufferGeometry9();
19160
19461
  geometry.setAttribute("position", new Float32BufferAttribute10(page.positions.buffer, 3));
@@ -19165,7 +19466,7 @@ var SectionMesh = class _SectionMesh extends Group3 {
19165
19466
  geometry.setIndex(new Uint32BufferAttribute(page.indices.buffer, 1));
19166
19467
  geometry.computeBoundingBox();
19167
19468
  geometry.computeBoundingSphere();
19168
- const transparent = page.layer === 2 ? atlases[page.placements[0].atlas].hasTranslucency : page.layer === 1;
19469
+ const transparent = page.layer === 2 ? atlases2[page.placements[0].atlas].hasTranslucency : page.layer === 1;
19169
19470
  const material = Materials.createShadedCanvasMaterial(canvas, transparent, false, true);
19170
19471
  material.side = FrontSide4;
19171
19472
  if (page.layer === 2) {
@@ -19180,7 +19481,7 @@ var SectionMesh = class _SectionMesh extends Group3 {
19180
19481
  section.blockIndices.set(mesh, page.blockIndices);
19181
19482
  const unsubscribe = [];
19182
19483
  for (const { atlas: id, x, y } of page.placements) {
19183
- const atlas = atlases[id];
19484
+ const atlas = atlases2[id];
19184
19485
  if (!atlas.hasAnimation) continue;
19185
19486
  unsubscribe.push(atlas.subscribe(() => {
19186
19487
  context.clearRect(x, y, atlas.image.width, atlas.image.height);
@@ -20523,6 +20824,7 @@ export {
20523
20824
  toRadians,
20524
20825
  toTrianglesDrawMode,
20525
20826
  trimCanvas,
20827
+ validateAnimationWeights,
20526
20828
  yieldToEventLoop
20527
20829
  };
20528
20830
  //# sourceMappingURL=index.mjs.map