minerender 2.1.0-alpha.52 → 2.1.0-alpha.54

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.
@@ -886,6 +886,71 @@ var init_Materials = __esm({
886
886
  alphaTest: 0.5
887
887
  });
888
888
  }
889
+ /** Whether vanilla's render type for the mode culls back faces. */
890
+ static entityModeCulls(mode = "cutout") {
891
+ return mode === "cutout_cull" || mode === "solid" || mode === "eyes" || mode === "water_mask";
892
+ }
893
+ /** UV offset per tick of entity age for the scrolling modes, as vanilla's texture matrix applies it. */
894
+ static entityModeScroll(mode = "cutout") {
895
+ if (mode === "energy_swirl") return [0.01, 0.01];
896
+ if (mode === "breeze_wind") return [0.02, 0];
897
+ return void 0;
898
+ }
899
+ /**
900
+ * Entity material for one of vanilla's render types. Entities are unlit here, so the lit and full-bright modes
901
+ * differ only in blending and depth state. Culling is not a material property: {@link entityModeCulls} decides
902
+ * whether the geometry gets inward faces.
903
+ */
904
+ static createEntityCanvasMaterial(canvas, mode = "cutout", tint) {
905
+ const material = _Materials.createBasicCanvasMaterial(canvas);
906
+ switch (mode) {
907
+ case "cutout":
908
+ case "cutout_cull":
909
+ break;
910
+ case "cutout_z_offset":
911
+ material.polygonOffset = true;
912
+ material.polygonOffsetFactor = -1;
913
+ material.polygonOffsetUnits = -10;
914
+ break;
915
+ case "solid":
916
+ material.alphaTest = 0;
917
+ break;
918
+ case "translucent":
919
+ case "breeze_wind":
920
+ material.transparent = true;
921
+ material.alphaTest = 0.1;
922
+ break;
923
+ case "translucent_emissive":
924
+ material.transparent = true;
925
+ material.alphaTest = 0.1;
926
+ material.depthWrite = false;
927
+ break;
928
+ case "eyes":
929
+ material.transparent = true;
930
+ material.alphaTest = 0;
931
+ material.depthWrite = false;
932
+ break;
933
+ case "energy_swirl":
934
+ material.transparent = true;
935
+ material.alphaTest = 0.1;
936
+ material.blending = import_three5.CustomBlending;
937
+ material.blendSrc = import_three5.OneFactor;
938
+ material.blendDst = import_three5.OneFactor;
939
+ material.blendSrcAlpha = import_three5.ZeroFactor;
940
+ material.blendDstAlpha = import_three5.OneFactor;
941
+ material.color.set(8421504);
942
+ break;
943
+ case "water_mask":
944
+ material.alphaTest = 0;
945
+ material.colorWrite = false;
946
+ break;
947
+ }
948
+ if (_Materials.entityModeScroll(mode)) {
949
+ material.map.wrapS = material.map.wrapT = import_three5.RepeatWrapping;
950
+ }
951
+ if (tint !== void 0) material.color.multiply(new import_three5.Color(tint));
952
+ return material;
953
+ }
889
954
  static createGuiCanvasMaterial(canvas) {
890
955
  const material = new import_three5.MeshBasicMaterial({
891
956
  map: Textures.createCanvasTexture(canvas),
@@ -3535,48 +3600,6 @@ var init_SceneObject = __esm({
3535
3600
  }
3536
3601
  });
3537
3602
 
3538
- // src/model/ModelCulling.ts
3539
- var import_three22, ModelCulling;
3540
- var init_ModelCulling = __esm({
3541
- "src/model/ModelCulling.ts"() {
3542
- import_three22 = require("three");
3543
- init_CubeFace();
3544
- ModelCulling = class {
3545
- static toLocalMask(worldMask, rotation) {
3546
- let localMask = 0;
3547
- for (const [localFace, offset] of CUBE_FACE_OFFSETS.entries()) {
3548
- const normal = new import_three22.Vector3(...offset).applyEuler(rotation);
3549
- const worldFace = CUBE_FACE_OFFSETS.findIndex(([x, y, z]) => Math.abs(normal.x - x) < 1e-6 && Math.abs(normal.y - y) < 1e-6 && Math.abs(normal.z - z) < 1e-6);
3550
- if (worldFace < 0) return 0;
3551
- if (worldMask & 1 << worldFace) localMask |= 1 << localFace;
3552
- }
3553
- return localMask;
3554
- }
3555
- static isOpaqueFullCube(atlas) {
3556
- if (!atlas || atlas.hasTransparency !== false || atlas.model.elements?.length !== 1) return false;
3557
- const element = atlas.model.elements[0];
3558
- if (element.from.length !== 3 || !element.from.every((value) => value === 0) || element.to.length !== 3 || !element.to.every((value) => value === 16) || element.rotation && element.rotation.angle !== 0 || element.mappedUv?.length !== 48 || !element.mappedUv.every(Number.isFinite)) return false;
3559
- if (!(atlas.image.width > 0 && atlas.image.height > 0)) return false;
3560
- return CUBE_FACES.every((face, faceIndex) => {
3561
- const texture = element.faces[face]?.texture;
3562
- if (!texture?.startsWith("#")) return false;
3563
- const key = texture.slice(1);
3564
- const position = atlas.positions[key];
3565
- const size = atlas.sizes[key];
3566
- if (position?.length !== 2 || !position.every(Number.isFinite) || size?.length !== 2 || !size.every((value) => value > 0 && Number.isFinite(value))) return false;
3567
- for (let vertex = 0; vertex < 4; vertex++) {
3568
- const uvIndex = (faceIndex * 4 + vertex) * 2;
3569
- const x = element.mappedUv[uvIndex] * atlas.image.width;
3570
- const y = (1 - element.mappedUv[uvIndex + 1]) * atlas.image.height;
3571
- if (x < position[0] - 1e-6 || x > position[0] + size[0] + 1e-6 || y < position[1] - 1e-6 || y > position[1] + size[1] + 1e-6) return false;
3572
- }
3573
- return true;
3574
- });
3575
- }
3576
- };
3577
- }
3578
- });
3579
-
3580
3603
  // src/three/BufferGeometryUtils.js
3581
3604
  function computeTangents(geometry) {
3582
3605
  geometry.computeTangents();
@@ -3589,7 +3612,7 @@ function mergeBufferGeometries(geometries, useGroups = false) {
3589
3612
  const attributes = {};
3590
3613
  const morphAttributes = {};
3591
3614
  const morphTargetsRelative = geometries[0].morphTargetsRelative;
3592
- const mergedGeometry = new import_three24.BufferGeometry();
3615
+ const mergedGeometry = new import_three19.BufferGeometry();
3593
3616
  let offset = 0;
3594
3617
  for (let i = 0; i < geometries.length; ++i) {
3595
3618
  const geometry = geometries[i];
@@ -3713,7 +3736,7 @@ function mergeBufferAttributes(attributes) {
3713
3736
  array.set(attributes[i].array, offset);
3714
3737
  offset += attributes[i].array.length;
3715
3738
  }
3716
- return new import_three24.BufferAttribute(array, itemSize, normalized);
3739
+ return new import_three19.BufferAttribute(array, itemSize, normalized);
3717
3740
  }
3718
3741
  function interleaveAttributes(attributes) {
3719
3742
  let TypedArray;
@@ -3729,7 +3752,7 @@ function interleaveAttributes(attributes) {
3729
3752
  arrayLength += attribute.array.length;
3730
3753
  stride += attribute.itemSize;
3731
3754
  }
3732
- const interleavedBuffer = new import_three24.InterleavedBuffer(new TypedArray(arrayLength), stride);
3755
+ const interleavedBuffer = new import_three19.InterleavedBuffer(new TypedArray(arrayLength), stride);
3733
3756
  let offset = 0;
3734
3757
  const res = [];
3735
3758
  const getters = ["getX", "getY", "getZ", "getW"];
@@ -3738,7 +3761,7 @@ function interleaveAttributes(attributes) {
3738
3761
  const attribute = attributes[j];
3739
3762
  const itemSize = attribute.itemSize;
3740
3763
  const count = attribute.count;
3741
- const iba = new import_three24.InterleavedBufferAttribute(interleavedBuffer, itemSize, offset, attribute.normalized);
3764
+ const iba = new import_three19.InterleavedBufferAttribute(interleavedBuffer, itemSize, offset, attribute.normalized);
3742
3765
  res.push(iba);
3743
3766
  offset += itemSize;
3744
3767
  for (let c = 0; c < count; c++) {
@@ -3822,13 +3845,13 @@ function mergeVertices(geometry, tolerance = 1e-4) {
3822
3845
  const name = attributeNames[i];
3823
3846
  const oldAttribute = geometry.getAttribute(name);
3824
3847
  const buffer = new oldAttribute.array.constructor(attrArrays[name]);
3825
- const attribute = new import_three24.BufferAttribute(buffer, oldAttribute.itemSize, oldAttribute.normalized);
3848
+ const attribute = new import_three19.BufferAttribute(buffer, oldAttribute.itemSize, oldAttribute.normalized);
3826
3849
  result.setAttribute(name, attribute);
3827
3850
  if (name in morphAttrsArrays) {
3828
3851
  for (let j = 0; j < morphAttrsArrays[name].length; j++) {
3829
3852
  const oldMorphAttribute = geometry.morphAttributes[name][j];
3830
3853
  const buffer2 = new oldMorphAttribute.array.constructor(morphAttrsArrays[name][j]);
3831
- const morphAttribute = new import_three24.BufferAttribute(buffer2, oldMorphAttribute.itemSize, oldMorphAttribute.normalized);
3854
+ const morphAttribute = new import_three19.BufferAttribute(buffer2, oldMorphAttribute.itemSize, oldMorphAttribute.normalized);
3832
3855
  result.morphAttributes[name][j] = morphAttribute;
3833
3856
  }
3834
3857
  }
@@ -3837,11 +3860,11 @@ function mergeVertices(geometry, tolerance = 1e-4) {
3837
3860
  return result;
3838
3861
  }
3839
3862
  function toTrianglesDrawMode(geometry, drawMode) {
3840
- if (drawMode === import_three24.TrianglesDrawMode) {
3863
+ if (drawMode === import_three19.TrianglesDrawMode) {
3841
3864
  console.warn("THREE.BufferGeometryUtils.toTrianglesDrawMode(): Geometry already defined as triangles.");
3842
3865
  return geometry;
3843
3866
  }
3844
- if (drawMode === import_three24.TriangleFanDrawMode || drawMode === import_three24.TriangleStripDrawMode) {
3867
+ if (drawMode === import_three19.TriangleFanDrawMode || drawMode === import_three19.TriangleStripDrawMode) {
3845
3868
  let index = geometry.getIndex();
3846
3869
  if (index === null) {
3847
3870
  const indices = [];
@@ -3859,7 +3882,7 @@ function toTrianglesDrawMode(geometry, drawMode) {
3859
3882
  }
3860
3883
  const numberOfTriangles = index.count - 2;
3861
3884
  const newIndices = [];
3862
- if (drawMode === import_three24.TriangleFanDrawMode) {
3885
+ if (drawMode === import_three19.TriangleFanDrawMode) {
3863
3886
  for (let i = 1; i <= numberOfTriangles; i++) {
3864
3887
  newIndices.push(index.getX(0));
3865
3888
  newIndices.push(index.getX(i));
@@ -3895,15 +3918,15 @@ function computeMorphedAttributes(object) {
3895
3918
  console.error("THREE.BufferGeometryUtils: Geometry is not of type BufferGeometry.");
3896
3919
  return null;
3897
3920
  }
3898
- const _vA = new import_three24.Vector3();
3899
- const _vB = new import_three24.Vector3();
3900
- const _vC = new import_three24.Vector3();
3901
- const _tempA = new import_three24.Vector3();
3902
- const _tempB = new import_three24.Vector3();
3903
- const _tempC = new import_three24.Vector3();
3904
- const _morphA = new import_three24.Vector3();
3905
- const _morphB = new import_three24.Vector3();
3906
- const _morphC = new import_three24.Vector3();
3921
+ const _vA = new import_three19.Vector3();
3922
+ const _vB = new import_three19.Vector3();
3923
+ const _vC = new import_three19.Vector3();
3924
+ const _tempA = new import_three19.Vector3();
3925
+ const _tempB = new import_three19.Vector3();
3926
+ const _tempC = new import_three19.Vector3();
3927
+ const _morphA = new import_three19.Vector3();
3928
+ const _morphB = new import_three19.Vector3();
3929
+ const _morphC = new import_three19.Vector3();
3907
3930
  function _calculateMorphedAttributeData(object2, material2, attribute, morphAttribute, morphTargetsRelative2, a2, b2, c2, modifiedAttributeArray) {
3908
3931
  _vA.fromBufferAttribute(attribute, a2);
3909
3932
  _vB.fromBufferAttribute(attribute, b2);
@@ -4098,8 +4121,8 @@ function computeMorphedAttributes(object) {
4098
4121
  }
4099
4122
  }
4100
4123
  }
4101
- const morphedPositionAttribute = new import_three24.Float32BufferAttribute(modifiedPosition, 3);
4102
- const morphedNormalAttribute = new import_three24.Float32BufferAttribute(modifiedNormal, 3);
4124
+ const morphedPositionAttribute = new import_three19.Float32BufferAttribute(modifiedPosition, 3);
4125
+ const morphedNormalAttribute = new import_three19.Float32BufferAttribute(modifiedNormal, 3);
4103
4126
  return {
4104
4127
  positionAttribute,
4105
4128
  normalAttribute,
@@ -4107,10 +4130,52 @@ function computeMorphedAttributes(object) {
4107
4130
  morphedNormalAttribute
4108
4131
  };
4109
4132
  }
4110
- var import_three24;
4133
+ var import_three19;
4111
4134
  var init_BufferGeometryUtils = __esm({
4112
4135
  "src/three/BufferGeometryUtils.js"() {
4113
- import_three24 = require("three");
4136
+ import_three19 = require("three");
4137
+ }
4138
+ });
4139
+
4140
+ // src/model/ModelCulling.ts
4141
+ var import_three20, ModelCulling;
4142
+ var init_ModelCulling = __esm({
4143
+ "src/model/ModelCulling.ts"() {
4144
+ import_three20 = require("three");
4145
+ init_CubeFace();
4146
+ ModelCulling = class {
4147
+ static toLocalMask(worldMask, rotation) {
4148
+ let localMask = 0;
4149
+ for (const [localFace, offset] of CUBE_FACE_OFFSETS.entries()) {
4150
+ const normal = new import_three20.Vector3(...offset).applyEuler(rotation);
4151
+ const worldFace = CUBE_FACE_OFFSETS.findIndex(([x, y, z]) => Math.abs(normal.x - x) < 1e-6 && Math.abs(normal.y - y) < 1e-6 && Math.abs(normal.z - z) < 1e-6);
4152
+ if (worldFace < 0) return 0;
4153
+ if (worldMask & 1 << worldFace) localMask |= 1 << localFace;
4154
+ }
4155
+ return localMask;
4156
+ }
4157
+ static isOpaqueFullCube(atlas) {
4158
+ if (!atlas || atlas.hasTransparency !== false || atlas.model.elements?.length !== 1) return false;
4159
+ const element = atlas.model.elements[0];
4160
+ if (element.from.length !== 3 || !element.from.every((value) => value === 0) || element.to.length !== 3 || !element.to.every((value) => value === 16) || element.rotation && element.rotation.angle !== 0 || element.mappedUv?.length !== 48 || !element.mappedUv.every(Number.isFinite)) return false;
4161
+ if (!(atlas.image.width > 0 && atlas.image.height > 0)) return false;
4162
+ return CUBE_FACES.every((face, faceIndex) => {
4163
+ const texture = element.faces[face]?.texture;
4164
+ if (!texture?.startsWith("#")) return false;
4165
+ const key = texture.slice(1);
4166
+ const position = atlas.positions[key];
4167
+ const size = atlas.sizes[key];
4168
+ if (position?.length !== 2 || !position.every(Number.isFinite) || size?.length !== 2 || !size.every((value) => value > 0 && Number.isFinite(value))) return false;
4169
+ for (let vertex = 0; vertex < 4; vertex++) {
4170
+ const uvIndex = (faceIndex * 4 + vertex) * 2;
4171
+ const x = element.mappedUv[uvIndex] * atlas.image.width;
4172
+ const y = (1 - element.mappedUv[uvIndex + 1]) * atlas.image.height;
4173
+ if (x < position[0] - 1e-6 || x > position[0] + size[0] + 1e-6 || y < position[1] - 1e-6 || y > position[1] + size[1] + 1e-6) return false;
4174
+ }
4175
+ return true;
4176
+ });
4177
+ }
4178
+ };
4114
4179
  }
4115
4180
  });
4116
4181
 
@@ -4118,13 +4183,13 @@ var init_BufferGeometryUtils = __esm({
4118
4183
  function isModelObject(obj) {
4119
4184
  return obj.isModelObject;
4120
4185
  }
4121
- var import_three25, import_ts_deepmerge5, p9, ModelObject;
4186
+ var import_three21, import_ts_deepmerge5, p7, ModelObject;
4122
4187
  var init_ModelObject = __esm({
4123
4188
  "src/model/scene/ModelObject.ts"() {
4124
4189
  init_SceneObject();
4125
4190
  init_Materials();
4126
4191
  init_UVMapper();
4127
- import_three25 = require("three");
4192
+ import_three21 = require("three");
4128
4193
  init_BufferGeometryUtils();
4129
4194
  init_model();
4130
4195
  import_ts_deepmerge5 = __toESM(require_dist());
@@ -4132,7 +4197,7 @@ var init_ModelObject = __esm({
4132
4197
  init_CubeFace();
4133
4198
  init_DisplayTransforms();
4134
4199
  init_ModelCulling();
4135
- p9 = prefix("ModelObject");
4200
+ p7 = prefix("ModelObject");
4136
4201
  ModelObject = class _ModelObject extends SceneObject {
4137
4202
  constructor(originalModel, options) {
4138
4203
  super(options);
@@ -4181,7 +4246,7 @@ var init_ModelObject = __esm({
4181
4246
  elGeo.clearGroups();
4182
4247
  }
4183
4248
  if (this.options.uvLockRotation) {
4184
- UVMapper.lockUvs(elGeo, el.faces, this.atlas, new import_three25.Euler(...this.options.uvLockRotation));
4249
+ UVMapper.lockUvs(elGeo, el.faces, this.atlas, new import_three21.Euler(...this.options.uvLockRotation));
4185
4250
  }
4186
4251
  if (this.options.tints) {
4187
4252
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
@@ -4190,19 +4255,19 @@ var init_ModelObject = __esm({
4190
4255
  if (tintIndex === void 0 || tintIndex < 0) continue;
4191
4256
  const tint = this.options.tints[tintIndex];
4192
4257
  if (tint === void 0) continue;
4193
- const color = new import_three25.Color(tint);
4258
+ const color = new import_three21.Color(tint);
4194
4259
  for (let vertex = 0; vertex < 4; vertex++) {
4195
4260
  color.toArray(colors, (faceIndex * 4 + vertex) * 3);
4196
4261
  }
4197
4262
  }
4198
- elGeo.setAttribute("color", new import_three25.BufferAttribute(colors, 3));
4263
+ elGeo.setAttribute("color", new import_three21.BufferAttribute(colors, 3));
4199
4264
  }
4200
- elGeo.applyMatrix4(new import_three25.Matrix4().makeTranslation((el.to[0] - el.from[0]) / 2, (el.to[1] - el.from[1]) / 2, (el.to[2] - el.from[2]) / 2));
4201
- elGeo.applyMatrix4(new import_three25.Matrix4().makeTranslation(el.from[0], el.from[1], el.from[2]));
4265
+ elGeo.applyMatrix4(new import_three21.Matrix4().makeTranslation((el.to[0] - el.from[0]) / 2, (el.to[1] - el.from[1]) / 2, (el.to[2] - el.from[2]) / 2));
4266
+ elGeo.applyMatrix4(new import_three21.Matrix4().makeTranslation(el.from[0], el.from[1], el.from[2]));
4202
4267
  if (el.rotation) {
4203
4268
  applyElementRotation(el.rotation, elGeo);
4204
4269
  }
4205
- elGeo.applyMatrix4(new import_three25.Matrix4().makeTranslation(-8, -8, -8));
4270
+ elGeo.applyMatrix4(new import_three21.Matrix4().makeTranslation(-8, -8, -8));
4206
4271
  if (displayTransform) DisplayTransforms.apply(elGeo, displayTransform);
4207
4272
  if (this.options.mergeMeshes) {
4208
4273
  allGeos.push(elGeo);
@@ -4214,17 +4279,17 @@ var init_ModelObject = __esm({
4214
4279
  }
4215
4280
  });
4216
4281
  } else {
4217
- console.debug(p9, this.atlas.model, "has no elements");
4282
+ console.debug(p7, this.atlas.model, "has no elements");
4218
4283
  }
4219
4284
  } else {
4220
- console.debug(p9, "Missing atlas for", this);
4285
+ console.debug(p7, "Missing atlas for", this);
4221
4286
  }
4222
4287
  if (this.options.mergeMeshes) {
4223
4288
  let combinedGeo;
4224
4289
  if (allGeos.length > 0) {
4225
4290
  combinedGeo = mergeBufferGeometries(allGeos);
4226
4291
  } else {
4227
- combinedGeo = new import_three25.BoxGeometry(16, 16, 16);
4292
+ combinedGeo = new import_three21.BoxGeometry(16, 16, 16);
4228
4293
  if (displayTransform) DisplayTransforms.apply(combinedGeo, displayTransform);
4229
4294
  }
4230
4295
  combinedGeo.computeBoundingBox();
@@ -4970,32 +5035,44 @@ var Entities = class {
4970
5035
  static async getLayerList(modelKey) {
4971
5036
  return Object.keys((await this.getModelFile(modelKey))?.layers ?? {});
4972
5037
  }
5038
+ /** The dataset's extra draws on top of `main`; their `when` labels are the states `EntityModelOptions.when` can enable. */
5039
+ static async getPassList(modelKey) {
5040
+ return (await this.getModelFile(modelKey))?.passes ?? [];
5041
+ }
4973
5042
  static async getEntity(modelKey, textureKey, options) {
4974
5043
  const model = await this.getModelFile(modelKey);
4975
5044
  if (!model) return void 0;
4976
- const names = [...new Set(options?.layers ?? [options?.layer ?? "main"])];
4977
- if (names.length === 0) throw new MineRenderError(`Entity ${model.id} requires at least one layer`);
4978
- const selected = names.map((name) => {
4979
- const layer = model.layers[name];
5045
+ const passes = options?.layers || options?.layer !== void 0 ? [] : (model.passes ?? []).filter((pass) => pass.when === void 0 || options?.when?.includes(pass.when));
5046
+ const draws = [...new Set(options?.layers ?? [options?.layer ?? "main"])].map((layer) => ({ layer }));
5047
+ if (draws.length === 0) throw new MineRenderError(`Entity ${model.id} requires at least one layer`);
5048
+ draws.push(...passes);
5049
+ const names = [];
5050
+ const selected = draws.map((pass) => {
5051
+ const layer = model.layers[pass.layer];
4980
5052
  if (!layer) {
4981
- throw new MineRenderError(`Entity ${model.id} has no layer "${name}". Available layers: ${Object.keys(model.layers).join(", ")}`);
5053
+ throw new MineRenderError(`Entity ${model.id} has no layer "${pass.layer}". Available layers: ${Object.keys(model.layers).join(", ")}`);
4982
5054
  }
4983
- return { name, layer };
5055
+ let name = pass.layer;
5056
+ for (let n = 2; names.includes(name); n++) name = `${pass.layer}#${n}`;
5057
+ names.push(name);
5058
+ return { name, layer, pass };
4984
5059
  });
4985
- const layers = Object.fromEntries(await Promise.all(selected.map(async ({ name, layer }, index) => {
5060
+ const layers = Object.fromEntries(await Promise.all(selected.map(async ({ name, layer, pass }, index) => {
4986
5061
  const override = options?.textures?.[name] ?? (index === 0 ? textureKey : void 0);
5062
+ const textureLocation = pass.textureLocation ?? layer.textureLocation;
4987
5063
  let texture;
4988
5064
  if (override) {
4989
5065
  const path = isAssetKey(override) ? override.getFullPath() : override.path;
4990
5066
  texture = isAssetKey(override) && override.assetType === "textures" && path.startsWith("entity/") ? override : new AssetKey(override.namespace, path, "textures", "entity", "assets", ".png", isAssetKey(override) ? override.root : void 0);
4991
- } else if (layer.textureLocation !== void 0) {
4992
- texture = AssetKey.parse("textures", layer.textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
5067
+ } else if (textureLocation !== void 0) {
5068
+ texture = AssetKey.parse("textures", textureLocation.replace(/^([^:]+:)?textures\//, "$1"));
4993
5069
  } else {
4994
5070
  texture = await this.resolveTexture(modelKey);
4995
5071
  }
4996
- return [name, { key: override ?? modelKey, texture, layer }];
5072
+ const render = pass.render ?? layer.render;
5073
+ return [name, { key: override ?? modelKey, texture, layer, ...render && { render }, ...pass.tint && { tint: pass.tint } }];
4997
5074
  })));
4998
- return { ...layers[names[0]], id: model.id, layers };
5075
+ return { ...layers[names[0]], id: model.id, layers, ...model.transform && { transform: model.transform } };
4999
5076
  }
5000
5077
  static async resolveTexture(modelKey) {
5001
5078
  const path = isAssetKey(modelKey) ? modelKey.getFullPath() : modelKey.path;
@@ -5078,6 +5155,7 @@ init_SceneObject();
5078
5155
  init_Caching();
5079
5156
  var import_ts_deepmerge4 = __toESM(require_dist());
5080
5157
  var import_three12 = require("three");
5158
+ init_Ticker();
5081
5159
  init_model();
5082
5160
  init_ModelTextures();
5083
5161
  init_AssetKey();
@@ -5089,10 +5167,14 @@ var EntityObject = class _EntityObject extends SceneObject {
5089
5167
  this.entity = entity;
5090
5168
  this.isEntityObject = true;
5091
5169
  this.meshesCreated = false;
5170
+ /** Scrolling materials are owned: their texture offset follows this entity's age. */
5171
+ this.scrollMaterials = [];
5172
+ /** Entity age in ticks, as vanilla's `ageInTicks`; drives the scrolling render modes. */
5173
+ this.age = 0;
5092
5174
  this.options = (0, import_ts_deepmerge4.default)({}, _EntityObject.DEFAULT_OPTIONS, options ?? {});
5093
5175
  }
5094
5176
  static {
5095
- this.DEFAULT_OPTIONS = (0, import_ts_deepmerge4.default)({}, SceneObject.DEFAULT_OPTIONS, { flip: true });
5177
+ this.DEFAULT_OPTIONS = (0, import_ts_deepmerge4.default)({}, SceneObject.DEFAULT_OPTIONS);
5096
5178
  }
5097
5179
  async init() {
5098
5180
  this.createMeshes();
@@ -5101,6 +5183,25 @@ var EntityObject = class _EntityObject extends SceneObject {
5101
5183
  dispose() {
5102
5184
  super.dispose();
5103
5185
  }
5186
+ disposeAndRemoveAllChildren() {
5187
+ this.clearScrollMaterials();
5188
+ super.disposeAndRemoveAllChildren();
5189
+ }
5190
+ clearScrollMaterials() {
5191
+ Ticker.remove(this.scrollTicker);
5192
+ this.scrollTicker = void 0;
5193
+ for (const { material } of this.scrollMaterials.splice(0)) {
5194
+ material.map?.dispose();
5195
+ material.dispose();
5196
+ }
5197
+ }
5198
+ /** Applies the entity age to the scrolling materials; the ticker calls this 20 times per second. */
5199
+ updateScroll() {
5200
+ for (const { offset, speed } of this.scrollMaterials) {
5201
+ offset.set(this.age * speed[0] % 1, 0 - this.age * speed[1] % 1);
5202
+ }
5203
+ this.notifyDirty();
5204
+ }
5104
5205
  getLayerGroup(name) {
5105
5206
  return super.getGroupByName(`layer:${name}`);
5106
5207
  }
@@ -5131,16 +5232,29 @@ var EntityObject = class _EntityObject extends SceneObject {
5131
5232
  createMeshes(force = false) {
5132
5233
  if (this.meshesCreated && !force) return;
5133
5234
  const modelRoot = new import_three12.Object3D();
5134
- if (this.options.flip) modelRoot.scale.set(-1, -1, 1);
5235
+ const transform = this.options.flip === void 0 ? this.entity.transform : void 0;
5236
+ if (transform) {
5237
+ for (const op of transform) {
5238
+ const matrix = new import_three12.Matrix4();
5239
+ if ("scale" in op) matrix.makeScale(...op.scale);
5240
+ else if ("translate" in op) matrix.makeTranslation(...op.translate);
5241
+ else matrix.makeRotationFromEuler(new import_three12.Euler(...op.rotate, "ZYX"));
5242
+ modelRoot.matrix.multiply(matrix);
5243
+ }
5244
+ modelRoot.matrixAutoUpdate = false;
5245
+ } else if (this.options.flip ?? true) {
5246
+ modelRoot.scale.set(-1, -1, 1);
5247
+ }
5135
5248
  this.add(modelRoot);
5136
5249
  Object.entries(this.entityLayers).forEach(([name, layer], index) => {
5137
5250
  const group = this.createGroup(`layer:${name}`);
5138
5251
  modelRoot.add(group);
5139
- this.createPart("root", layer.layer.root, group, layer.layer.texture, Materials.MISSING_TEXTURE, index);
5252
+ const inward = !Materials.entityModeCulls(layer.render ?? layer.layer.render);
5253
+ this.createPart("root", layer.layer.root, group, layer.layer.texture, Materials.MISSING_TEXTURE, index, inward);
5140
5254
  });
5141
5255
  this.meshesCreated = true;
5142
5256
  }
5143
- createPart(name, part, parent, textureSize, material, renderOrder) {
5257
+ createPart(name, part, parent, textureSize, material, renderOrder, inward = true) {
5144
5258
  const anchor = this.createGroup(name);
5145
5259
  anchor.position.fromArray(part.pose.offset);
5146
5260
  anchor.rotation.set(...part.pose.rotation, "ZYX");
@@ -5159,7 +5273,7 @@ var EntityObject = class _EntityObject extends SceneObject {
5159
5273
  uv
5160
5274
  ).clone();
5161
5275
  geometry.translate(cube.origin[0] + width / 2, cube.origin[1] + height / 2, cube.origin[2] + depth / 2);
5162
- if (Math.min(width + growX * 2, height + growY * 2, depth + growZ * 2) > 0) {
5276
+ if (inward && Math.min(width + growX * 2, height + growY * 2, depth + growZ * 2) > 0) {
5163
5277
  const index = Array.from(geometry.getIndex().array);
5164
5278
  geometry.setIndex(index.concat(index.slice().reverse()));
5165
5279
  }
@@ -5169,26 +5283,42 @@ var EntityObject = class _EntityObject extends SceneObject {
5169
5283
  if (this.options.wireframe) addWireframeToMesh(geometry, mesh);
5170
5284
  }
5171
5285
  for (const [childName, child] of Object.entries(part.children)) {
5172
- this.createPart(childName, child, anchor, size, material, renderOrder);
5286
+ this.createPart(childName, child, anchor, size, material, renderOrder, inward);
5173
5287
  }
5174
5288
  }
5175
5289
  async applyTextures() {
5290
+ this.clearScrollMaterials();
5176
5291
  await Promise.all(Object.entries(this.entityLayers).map(async ([name, layer]) => {
5292
+ const mode = layer.render ?? layer.layer.render ?? "cutout";
5293
+ const tint = layer.tint === void 0 ? void 0 : this.options.tints?.[layer.tint];
5294
+ const scroll = Materials.entityModeScroll(mode);
5177
5295
  const assetKeyStr = this.getTextureKey(layer).serialize();
5178
- const keyStr = `entity:${assetKeyStr}`;
5179
- let mat = Caching.materialCache.getIfPresent(keyStr);
5296
+ const keyStr = `entity:${mode}:${tint === void 0 ? "" : new import_three12.Color(tint).getHexString()}:${assetKeyStr}`;
5297
+ let mat = scroll ? void 0 : Caching.materialCache.getIfPresent(keyStr);
5180
5298
  if (!mat) {
5181
5299
  const cachedAsset = Caching.textureAssetCache.getIfPresent(assetKeyStr);
5182
5300
  const imageData = await this.loadTextures(layer);
5183
5301
  if (!imageData) return;
5184
5302
  const canvas = imageData.data.canvas;
5185
- const createMaterial = () => Materials.createBasicCanvasMaterial(canvas);
5186
- mat = cachedAsset && Caching.textureAssetCache.getIfPresent(assetKeyStr) === cachedAsset ? Caching.materialCache.get(keyStr, createMaterial) : createMaterial();
5303
+ const createMaterial = () => Materials.createEntityCanvasMaterial(canvas, mode, tint);
5304
+ mat = !scroll && cachedAsset && Caching.textureAssetCache.getIfPresent(assetKeyStr) === cachedAsset ? Caching.materialCache.get(keyStr, createMaterial) : createMaterial();
5305
+ if (scroll) {
5306
+ this.scrollMaterials.push({ material: mat, offset: mat.map.offset, speed: scroll });
5307
+ }
5187
5308
  }
5188
5309
  this.getLayerGroup(name)?.traverse((object) => {
5189
5310
  if (isMesh(object)) object.material = mat;
5190
5311
  });
5191
5312
  }));
5313
+ if (!this.children.length) {
5314
+ this.clearScrollMaterials();
5315
+ } else if (this.scrollMaterials.length) {
5316
+ this.updateScroll();
5317
+ this.scrollTicker = Ticker.add(() => {
5318
+ this.age++;
5319
+ this.updateScroll();
5320
+ });
5321
+ }
5192
5322
  this.notifyDirty();
5193
5323
  }
5194
5324
  };
@@ -7704,27 +7834,97 @@ function copyDataTexture(source) {
7704
7834
  }
7705
7835
 
7706
7836
  // src/gui/GuiHelper.ts
7837
+ init_AssetKey();
7707
7838
  var GuiHelper = class {
7708
7839
  static inventorySlot(slot, origin = [0, 0], offset = [18, 18], rowSize = 9) {
7709
7840
  const [column, row] = typeof slot === "number" ? [slot % rowSize, Math.floor(slot / rowSize)] : slot;
7710
7841
  return [origin[0] + column * offset[0], origin[1] + row * offset[1]];
7711
7842
  }
7843
+ /** Creates crafting-table layers for ingredients and the result; stack counts are not drawn. */
7844
+ static recipe(recipe, options = {}) {
7845
+ const layers = [
7846
+ { name: "background", texture: "minecraft:gui/container/crafting_table", crop: [0, 0, 176, 166] }
7847
+ ];
7848
+ const itemKey = (ingredient) => {
7849
+ const item = typeof ingredient === "string" && !ingredient.startsWith("#") ? ingredient : typeof ingredient === "object" && "item" in ingredient ? ingredient.item : options.resolveIngredient?.(ingredient);
7850
+ if (isAssetKey(item)) return item;
7851
+ if (!item || item.startsWith("#")) {
7852
+ throw new Error(`Select a concrete item for recipe ingredient ${JSON.stringify(ingredient)}`);
7853
+ }
7854
+ const key = AssetKey.parse("models", item);
7855
+ return new AssetKey(key.namespace, key.getFullPath(), "models", "item");
7856
+ };
7857
+ const addIngredient = (ingredient, slot) => {
7858
+ layers.push({
7859
+ name: `ingredient-${slot}`,
7860
+ item: itemKey(ingredient),
7861
+ position: this.inventorySlot(slot, [30, 17], [18, 18], 3)
7862
+ });
7863
+ };
7864
+ switch (recipe.type) {
7865
+ case "minecraft:crafting_shaped":
7866
+ case "crafting_shaped": {
7867
+ const pattern = recipe.pattern;
7868
+ if (!pattern.length || pattern.length > 3 || !pattern[0].length || pattern[0].length > 3 || pattern.some((row) => row.length !== pattern[0].length)) {
7869
+ throw new Error("Crafting patterns must be rectangular, with 1\u20133 rows and columns");
7870
+ }
7871
+ const cells = pattern.flatMap((row, y) => row.split("").map((symbol, x) => ({ symbol, x, y }))).filter((cell) => cell.symbol !== " ");
7872
+ if (!cells.length) throw new Error("Crafting patterns must contain an ingredient");
7873
+ const minX = Math.min(...cells.map((cell) => cell.x)), maxX = Math.max(...cells.map((cell) => cell.x));
7874
+ const minY = Math.min(...cells.map((cell) => cell.y)), maxY = Math.max(...cells.map((cell) => cell.y));
7875
+ const offsetX = minX === maxX ? 1 : 0, offsetY = minY === maxY ? 1 : 0;
7876
+ for (const { symbol, x, y } of cells) {
7877
+ const ingredient = recipe.key[symbol];
7878
+ if (!ingredient) throw new Error(`Missing recipe ingredient for symbol ${JSON.stringify(symbol)}`);
7879
+ addIngredient(ingredient, (y - minY + offsetY) * 3 + x - minX + offsetX);
7880
+ }
7881
+ break;
7882
+ }
7883
+ case "minecraft:crafting_shapeless":
7884
+ case "crafting_shapeless":
7885
+ if (!recipe.ingredients.length || recipe.ingredients.length > 9) {
7886
+ throw new Error("Shapeless crafting recipes require 1\u20139 ingredients");
7887
+ }
7888
+ recipe.ingredients.forEach(addIngredient);
7889
+ break;
7890
+ default:
7891
+ throw new Error(`Unsupported crafting recipe type: ${recipe.type}`);
7892
+ }
7893
+ layers.push({
7894
+ name: "result",
7895
+ item: itemKey("id" in recipe.result ? recipe.result.id : recipe.result.item),
7896
+ position: [124, 35]
7897
+ });
7898
+ return layers;
7899
+ }
7712
7900
  };
7713
7901
 
7714
7902
  // src/gui/scene/GuiObject.ts
7715
- var import_three19 = require("three");
7903
+ var import_three22 = require("three");
7716
7904
  init_AssetKey();
7717
7905
  init_ModelTextures();
7906
+ init_Models();
7718
7907
  init_Caching();
7719
7908
  init_SceneObject();
7720
7909
  init_Materials();
7910
+ init_ModelObject();
7911
+ init_DisplayPosition();
7912
+
7913
+ // src/model/GuiLight.ts
7914
+ var GuiLight = /* @__PURE__ */ ((GuiLight2) => {
7915
+ GuiLight2["FRONT"] = "front";
7916
+ GuiLight2["SIDE"] = "side";
7917
+ return GuiLight2;
7918
+ })(GuiLight || {});
7919
+
7920
+ // src/gui/scene/GuiObject.ts
7721
7921
  var GuiObject = class extends SceneObject {
7722
7922
  constructor(textureLayers, options) {
7723
7923
  super({ ...options, instanceMeshes: false, mergeMeshes: false });
7724
7924
  this.textureLayers = textureLayers;
7725
7925
  this.isGuiObject = true;
7726
7926
  /** Local bounds in GUI pixels, with y growing down from the origin. */
7727
- this.bounds = new import_three19.Box2();
7927
+ this.bounds = new import_three22.Box2();
7728
7928
  this.geometries = /* @__PURE__ */ new Set();
7729
7929
  this.ownedMaterials = /* @__PURE__ */ new Set();
7730
7930
  this.initialized = false;
@@ -7732,14 +7932,35 @@ var GuiObject = class extends SceneObject {
7732
7932
  async init() {
7733
7933
  if (this.initialized) return;
7734
7934
  try {
7935
+ let depth = 0;
7735
7936
  for (const [index, layer] of this.textureLayers.entries()) {
7937
+ const [x, y] = layer.position ?? [0, 0];
7938
+ if ("item" in layer) {
7939
+ const [width2, height2] = layer.size ?? [16, 16];
7940
+ const item = await this.createItem(layer, index);
7941
+ item.position.set(x + width2 / 2, -y - height2 / 2, 0);
7942
+ item.scale.set(width2 / 16, height2 / 16, width2 / 16);
7943
+ item.updateMatrix();
7944
+ const bounds = new import_three22.Box3();
7945
+ item.iterateAllMeshes((mesh2) => {
7946
+ mesh2.geometry.computeBoundingBox();
7947
+ bounds.union(mesh2.geometry.boundingBox);
7948
+ });
7949
+ bounds.applyMatrix4(item.matrix);
7950
+ item.position.z = depth - bounds.min.z;
7951
+ depth += bounds.max.z - bounds.min.z + 0.01;
7952
+ this.bounds.expandByPoint(new import_three22.Vector2(x, y));
7953
+ this.bounds.expandByPoint(new import_three22.Vector2(x + width2, y + height2));
7954
+ this.bounds.expandByPoint(new import_three22.Vector2(bounds.min.x, -bounds.max.y));
7955
+ this.bounds.expandByPoint(new import_three22.Vector2(bounds.max.x, -bounds.min.y));
7956
+ continue;
7957
+ }
7736
7958
  const key = typeof layer.texture === "string" ? AssetKey.parse("textures", layer.texture) : layer.texture;
7737
7959
  const material = await this.loadMaterial(key);
7738
7960
  const image = material.map.image;
7739
7961
  const [cropX, cropY, cropWidth, cropHeight] = layer.crop ?? [0, 0, image.width, image.height];
7740
7962
  const [width, height] = layer.size ?? [cropWidth, cropHeight];
7741
- const [x, y] = layer.position ?? [0, 0];
7742
- const geometry = new import_three19.PlaneGeometry(width, height);
7963
+ const geometry = new import_three22.PlaneGeometry(width, height);
7743
7964
  this.geometries.add(geometry);
7744
7965
  const uv = geometry.getAttribute("uv");
7745
7966
  for (let i = 0; i < uv.count; i++) {
@@ -7749,14 +7970,16 @@ var GuiObject = class extends SceneObject {
7749
7970
  1 - (cropY + (1 - uv.getY(i)) * cropHeight) / image.height
7750
7971
  );
7751
7972
  }
7752
- const mesh = new import_three19.Mesh(geometry, material);
7973
+ const mesh = new import_three22.Mesh(geometry, material);
7753
7974
  mesh.name = `mesh:${layer.name ?? index}`;
7754
- mesh.position.set(x + width / 2, -y - height / 2, 0);
7975
+ mesh.position.set(x + width / 2, -y - height / 2, depth);
7755
7976
  mesh.renderOrder = index;
7756
7977
  this.add(mesh);
7757
- this.bounds.expandByPoint(new import_three19.Vector2(x, y));
7758
- this.bounds.expandByPoint(new import_three19.Vector2(x + width, y + height));
7978
+ this.bounds.expandByPoint(new import_three22.Vector2(x, y));
7979
+ this.bounds.expandByPoint(new import_three22.Vector2(x + width, y + height));
7980
+ depth += 0.01;
7759
7981
  }
7982
+ for (const child of this.children) child.position.z -= depth - 0.01;
7760
7983
  this.initialized = true;
7761
7984
  this.notifyDirty();
7762
7985
  } catch (error) {
@@ -7764,6 +7987,32 @@ var GuiObject = class extends SceneObject {
7764
7987
  throw error;
7765
7988
  }
7766
7989
  }
7990
+ async createItem(layer, index) {
7991
+ const key = typeof layer.item === "string" ? AssetKey.parse("models", layer.item) : layer.item;
7992
+ const model = await Models.getMerged(key);
7993
+ if (!model) throw new Error(`Could not load GUI item ${key.toNamespacedString()}`);
7994
+ const item = new ModelObject(model, {
7995
+ displayPosition: "gui" /* GUI */,
7996
+ tints: layer.tints,
7997
+ instanceMeshes: false,
7998
+ mergeMeshes: true
7999
+ });
8000
+ item.name = `group:${layer.name ?? index}`;
8001
+ this.add(item);
8002
+ await item.init();
8003
+ item.iterateAllMeshes((mesh) => {
8004
+ this.geometries.add(mesh.geometry);
8005
+ mesh.name = `mesh:${layer.name ?? index}`;
8006
+ mesh.renderOrder = index;
8007
+ for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
8008
+ material.transparent = true;
8009
+ if (model.gui_light === "front" /* FRONT */ && material.uniforms?.SHADE) {
8010
+ material.uniforms.SHADE.value = false;
8011
+ }
8012
+ }
8013
+ });
8014
+ return item;
8015
+ }
7767
8016
  async loadMaterial(key) {
7768
8017
  const assetKey = key.serialize();
7769
8018
  const materialKey = `gui:${assetKey}`;
@@ -7802,21 +8051,21 @@ function isGuiObject(obj) {
7802
8051
  init_image();
7803
8052
 
7804
8053
  // src/inspector/SceneInspector.ts
7805
- var import_three20 = require("three");
8054
+ var import_three23 = require("three");
7806
8055
  init_SceneObject();
7807
8056
  init_util();
7808
8057
  init_log();
7809
8058
  init_three();
7810
- var p7 = prefix("SceneInspector");
8059
+ var p8 = prefix("SceneInspector");
7811
8060
  var help = "<span>Ctrl/Cmd+Click to Select<br/></span><br/>";
7812
8061
  var SceneInspector = class {
7813
8062
  constructor(renderer) {
7814
8063
  this.renderer = renderer;
7815
- this.mouse = new import_three20.Vector2();
8064
+ this.mouse = new import_three23.Vector2();
7816
8065
  this.listeners = new AbortController();
7817
8066
  this.objectInfoContainer = document.createElement("div");
7818
8067
  this.objectControlsContainer = document.createElement("div");
7819
- this.raycaster = new import_three20.Raycaster();
8068
+ this.raycaster = new import_three23.Raycaster();
7820
8069
  this.init();
7821
8070
  }
7822
8071
  init() {
@@ -7901,7 +8150,7 @@ var SceneInspector = class {
7901
8150
  }
7902
8151
  selectObject(targetObject, targetIntersection, instance = this.getIntersectionInstance(targetIntersection)) {
7903
8152
  this.selectedObject = targetObject;
7904
- console.log(p7, "selected", targetObject);
8153
+ console.log(p8, "selected", targetObject);
7905
8154
  this.redraw(targetObject, targetIntersection, instance);
7906
8155
  }
7907
8156
  redraw(targetObject, targetIntersection, instance) {
@@ -8160,7 +8409,7 @@ var SceneStatsDisplay = class {
8160
8409
 
8161
8410
  // src/instance/InstanceManager.ts
8162
8411
  init_log();
8163
- var p8 = prefix("InstanceManager");
8412
+ var p9 = prefix("InstanceManager");
8164
8413
  var InstanceManager = class {
8165
8414
  constructor() {
8166
8415
  this.isInstanceManager = true;
@@ -8168,7 +8417,7 @@ var InstanceManager = class {
8168
8417
  }
8169
8418
  async get(key) {
8170
8419
  if (key in this.instanceCache) {
8171
- console.debug(p8, "key in cache", key);
8420
+ console.debug(p9, "key in cache", key);
8172
8421
  return (await this.instanceCache[key]).nextInstance();
8173
8422
  }
8174
8423
  return void 0;
@@ -8177,7 +8426,7 @@ var InstanceManager = class {
8177
8426
  if (key in this.instanceCache) {
8178
8427
  return await this.get(key);
8179
8428
  }
8180
- console.debug(p8, "key not in cache", key);
8429
+ console.debug(p9, "key not in cache", key);
8181
8430
  const pending = Promise.resolve().then(supplier).then((obj) => obj.nextInstance());
8182
8431
  this.instanceCache[key] = pending;
8183
8432
  try {
@@ -8211,13 +8460,6 @@ function isInstanceable(obj) {
8211
8460
  init_DisplayPosition();
8212
8461
  init_DisplayTransforms();
8213
8462
 
8214
- // src/model/GuiLight.ts
8215
- var GuiLight = /* @__PURE__ */ ((GuiLight2) => {
8216
- GuiLight2["FRONT"] = "front";
8217
- GuiLight2["SIDE"] = "side";
8218
- return GuiLight2;
8219
- })(GuiLight || {});
8220
-
8221
8463
  // src/model/Model.ts
8222
8464
  var ITEM_GENERATED = "item/generated";
8223
8465
  var BUILTIN_GENERATED = "builtin/generated";
@@ -8239,7 +8481,7 @@ init_ModelGenerator();
8239
8481
  init_ModelMerger();
8240
8482
 
8241
8483
  // src/model/block/BlockStateResolver.ts
8242
- var import_three23 = require("three");
8484
+ var import_three25 = require("three");
8243
8485
  init_BlockStates();
8244
8486
  init_MineRenderError();
8245
8487
  init_util();
@@ -8317,7 +8559,7 @@ var BlockStateResolver = class {
8317
8559
  return variants[variants.length - 1];
8318
8560
  }
8319
8561
  static rotation(variant) {
8320
- const rotation = new import_three23.Euler();
8562
+ const rotation = new import_three25.Euler();
8321
8563
  if (typeof variant.x !== "undefined") {
8322
8564
  rotation.x = toRadians(clampRotationDegrees(variant.x));
8323
8565
  }