minerender 2.1.0-alpha.73 → 2.1.0-alpha.75

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.
@@ -291,7 +291,7 @@ declare class Materials {
291
291
  */
292
292
  static createEntityCanvasMaterial(canvas: HTMLCanvasElement, mode?: EntityRenderMode, tint?: ColorRepresentation): MeshBasicMaterial;
293
293
  static createGuiCanvasMaterial(canvas: HTMLCanvasElement): MeshBasicMaterial;
294
- static createShadedCanvasMaterial(canvas: HTMLCanvasElement, transparent?: boolean, shade?: boolean): Material;
294
+ static createShadedCanvasMaterial(canvas: HTMLCanvasElement, transparent?: boolean, shade?: boolean, boundUvs?: boolean): Material;
295
295
  static getImage(key: MaterialKey): Material;
296
296
  static needsUpdate(mat: Material): void;
297
297
  }
@@ -693,6 +693,8 @@ declare class UVMapper {
693
693
  static setCubeUvs(geometry: BoxGeometry, originalTextureSize: DoubleArray, actualTextureSize: DoubleArray, faces: ModelFaces): void;
694
694
  static facesToUvArray(faces: ModelFaces, originalTextureSize: DoubleArray, actualTextureSize: DoubleArray): number[];
695
695
  static facesToUv(faces: ModelFaces, originalTextureSize: DoubleArray, actualTextureSize: DoubleArray): Float32BufferAttribute;
696
+ /** Bounds texture sampling to texel centres without changing cropped face UVs. */
697
+ static setAtlasUvBounds(geometry: BoxGeometry, faces: ModelFaces, atlas: TextureAtlas): void;
696
698
  static lockUvs(geometry: BoxGeometry, faces: ModelFaces, atlas: TextureAtlas, rotation: Euler): void;
697
699
  static getAtlas(model: Model): Promise<Maybe<TextureAtlas>>;
698
700
  static fillMissingTextureKeys<T>(textures: ModelTextures, target: {
@@ -2515,6 +2517,12 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
2515
2517
  private placeBlocks;
2516
2518
  /** Replaces one chunk column's blocks. Entity NBT remains available on the parsed chunk. */
2517
2519
  placeChunk(chunk: AnvilChunk, executor?: BatchedExecutor): Promise<void>;
2520
+ /**
2521
+ * Removes all sections at integer chunk-column coordinates, then refreshes neighboring faces and fluids.
2522
+ * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
2523
+ */
2524
+ unloadChunkColumn(x: number, z: number): Promise<void>;
2525
+ private clearChunkColumn;
2518
2526
  clear(): Promise<void>;
2519
2527
  private updateCulling;
2520
2528
  private getOrCreateChunkAt;
@@ -291,7 +291,7 @@ declare class Materials {
291
291
  */
292
292
  static createEntityCanvasMaterial(canvas: HTMLCanvasElement, mode?: EntityRenderMode, tint?: ColorRepresentation): MeshBasicMaterial;
293
293
  static createGuiCanvasMaterial(canvas: HTMLCanvasElement): MeshBasicMaterial;
294
- static createShadedCanvasMaterial(canvas: HTMLCanvasElement, transparent?: boolean, shade?: boolean): Material;
294
+ static createShadedCanvasMaterial(canvas: HTMLCanvasElement, transparent?: boolean, shade?: boolean, boundUvs?: boolean): Material;
295
295
  static getImage(key: MaterialKey): Material;
296
296
  static needsUpdate(mat: Material): void;
297
297
  }
@@ -693,6 +693,8 @@ declare class UVMapper {
693
693
  static setCubeUvs(geometry: BoxGeometry, originalTextureSize: DoubleArray, actualTextureSize: DoubleArray, faces: ModelFaces): void;
694
694
  static facesToUvArray(faces: ModelFaces, originalTextureSize: DoubleArray, actualTextureSize: DoubleArray): number[];
695
695
  static facesToUv(faces: ModelFaces, originalTextureSize: DoubleArray, actualTextureSize: DoubleArray): Float32BufferAttribute;
696
+ /** Bounds texture sampling to texel centres without changing cropped face UVs. */
697
+ static setAtlasUvBounds(geometry: BoxGeometry, faces: ModelFaces, atlas: TextureAtlas): void;
696
698
  static lockUvs(geometry: BoxGeometry, faces: ModelFaces, atlas: TextureAtlas, rotation: Euler): void;
697
699
  static getAtlas(model: Model): Promise<Maybe<TextureAtlas>>;
698
700
  static fillMissingTextureKeys<T>(textures: ModelTextures, target: {
@@ -2515,6 +2517,12 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
2515
2517
  private placeBlocks;
2516
2518
  /** Replaces one chunk column's blocks. Entity NBT remains available on the parsed chunk. */
2517
2519
  placeChunk(chunk: AnvilChunk, executor?: BatchedExecutor): Promise<void>;
2520
+ /**
2521
+ * Removes all sections at integer chunk-column coordinates, then refreshes neighboring faces and fluids.
2522
+ * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
2523
+ */
2524
+ unloadChunkColumn(x: number, z: number): Promise<void>;
2525
+ private clearChunkColumn;
2518
2526
  clear(): Promise<void>;
2519
2527
  private updateCulling;
2520
2528
  private getOrCreateChunkAt;
@@ -982,7 +982,7 @@ var init_Materials = __esm({
982
982
  };
983
983
  return material;
984
984
  }
985
- static createShadedCanvasMaterial(canvas, transparent = false, shade = false) {
985
+ static createShadedCanvasMaterial(canvas, transparent = false, shade = false, boundUvs = false) {
986
986
  const vertShader = `
987
987
  uniform bool SHADE;
988
988
 
@@ -992,6 +992,10 @@ var init_Materials = __esm({
992
992
  #else
993
993
  varying vec2 vUv;
994
994
  #endif
995
+ #ifdef BOUND_UVS
996
+ attribute vec4 uvBounds;
997
+ flat out vec4 vUvBounds;
998
+ #endif
995
999
  varying vec3 vTint;
996
1000
  varying float light;
997
1001
  varying float lift;
@@ -1027,13 +1031,17 @@ var init_Materials = __esm({
1027
1031
  }
1028
1032
 
1029
1033
  vUv = uv;
1034
+ #ifdef BOUND_UVS
1035
+ vUvBounds = uvBounds;
1036
+ #endif
1030
1037
  vTint = color;
1031
1038
 
1039
+ vec4 mvPosition = vec4(position, 1.0);
1032
1040
  #ifdef USE_INSTANCING
1033
- gl_Position = projectionMatrix * viewMatrix * modelMatrix * instanceMatrix * vec4(position, 1.0);
1034
- #else
1035
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
1041
+ mvPosition = instanceMatrix * mvPosition;
1036
1042
  #endif
1043
+ mvPosition = modelViewMatrix * mvPosition;
1044
+ gl_Position = projectionMatrix * mvPosition;
1037
1045
 
1038
1046
  }
1039
1047
  `;
@@ -1053,13 +1061,22 @@ var init_Materials = __esm({
1053
1061
  #else
1054
1062
  varying vec2 vUv;
1055
1063
  #endif
1064
+ #ifdef BOUND_UVS
1065
+ flat in vec4 vUvBounds;
1066
+ #endif
1056
1067
  varying vec3 vTint;
1057
1068
  varying float light;
1058
1069
  varying float lift;
1059
1070
 
1060
1071
  void main(void)
1061
1072
  {
1062
- vec4 color = texture2D(map, vUv);
1073
+ #ifdef BOUND_UVS
1074
+ // Keep edge samples inside their texture even without centroid interpolation.
1075
+ vec2 mapUv = clamp(vUv, vUvBounds.xy, vUvBounds.zw);
1076
+ #else
1077
+ vec2 mapUv = vUv;
1078
+ #endif
1079
+ vec4 color = texture2D(map, mapUv);
1063
1080
  color.rgb *= vTint;
1064
1081
 
1065
1082
  if (color.a < 0.01) discard;
@@ -1080,7 +1097,8 @@ var init_Materials = __esm({
1080
1097
  }
1081
1098
  `;
1082
1099
  try {
1083
- return new import_three5.ShaderMaterial({
1100
+ const material = new import_three5.ShaderMaterial({
1101
+ defines: boundUvs ? { BOUND_UVS: true } : {},
1084
1102
  uniforms: {
1085
1103
  SHADE: { value: true },
1086
1104
  BRIGHTNESS: { value: 1 },
@@ -1098,6 +1116,8 @@ var init_Materials = __esm({
1098
1116
  side: transparent ? import_three5.DoubleSide : import_three5.FrontSide,
1099
1117
  alphaTest: 0.5
1100
1118
  });
1119
+ Object.assign(material.defaultAttributeValues, { uvBounds: [0, 0, 1, 1] });
1120
+ return material;
1101
1121
  } catch (e) {
1102
1122
  console.warn(e);
1103
1123
  }
@@ -2979,6 +2999,37 @@ var init_UVMapper = __esm({
2979
2999
  static facesToUv(faces, originalTextureSize, actualTextureSize) {
2980
3000
  return new import_three9.Float32BufferAttribute(this.facesToUvArray(faces, originalTextureSize, actualTextureSize), 2);
2981
3001
  }
3002
+ /** Bounds texture sampling to texel centres without changing cropped face UVs. */
3003
+ static setAtlasUvBounds(geometry, faces, atlas) {
3004
+ const uv = geometry.getAttribute("uv");
3005
+ const bounds = new Float32Array(uv.count * 4);
3006
+ for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
3007
+ const texture = faces[faceName]?.texture?.substring(1);
3008
+ const position = texture && atlas.positions[texture];
3009
+ const size = texture && atlas.sizes[texture];
3010
+ let minU = Infinity, minV = Infinity, maxU = -Infinity, maxV = -Infinity;
3011
+ if (position && size) {
3012
+ minU = position[0] / atlas.image.width;
3013
+ maxU = (position[0] + size[0]) / atlas.image.width;
3014
+ minV = 1 - (position[1] + size[1]) / atlas.image.height;
3015
+ maxV = 1 - position[1] / atlas.image.height;
3016
+ } else {
3017
+ for (let corner = 0; corner < 4; corner++) {
3018
+ const index = faceIndex * 4 + corner;
3019
+ minU = Math.min(minU, uv.getX(index));
3020
+ minV = Math.min(minV, uv.getY(index));
3021
+ maxU = Math.max(maxU, uv.getX(index));
3022
+ maxV = Math.max(maxV, uv.getY(index));
3023
+ }
3024
+ }
3025
+ const insetU = Math.min(0.5 / atlas.image.width, (maxU - minU) / 2);
3026
+ const insetV = Math.min(0.5 / atlas.image.height, (maxV - minV) / 2);
3027
+ for (let corner = 0; corner < 4; corner++) {
3028
+ bounds.set([minU + insetU, minV + insetV, maxU - insetU, maxV - insetV], (faceIndex * 4 + corner) * 4);
3029
+ }
3030
+ }
3031
+ geometry.setAttribute("uvBounds", new import_three9.Float32BufferAttribute(bounds, 4));
3032
+ }
2982
3033
  static lockUvs(geometry, faces, atlas, rotation) {
2983
3034
  const uv = geometry.getAttribute("uv");
2984
3035
  for (let faceIndex = 0; faceIndex < CUBE_FACES.length; faceIndex++) {
@@ -5376,6 +5427,7 @@ var init_ModelObject = __esm({
5376
5427
  if (this.options.uvLockRotation) {
5377
5428
  UVMapper.lockUvs(elGeo, el.faces, this.atlas, new import_three25.Euler(...this.options.uvLockRotation));
5378
5429
  }
5430
+ UVMapper.setAtlasUvBounds(elGeo, el.faces, this.atlas);
5379
5431
  if (this.options.tints) {
5380
5432
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
5381
5433
  for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
@@ -5441,7 +5493,7 @@ var init_ModelObject = __esm({
5441
5493
  }
5442
5494
  applyTextures() {
5443
5495
  if (this.atlas) {
5444
- const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false);
5496
+ const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false, true);
5445
5497
  this.atlasMaterial = mat;
5446
5498
  this.atlasTexture = mat.uniforms?.map?.value ?? mat.map;
5447
5499
  this.iterateAllMeshes((mesh) => {
@@ -15355,13 +15407,7 @@ var MineRenderWorld = class {
15355
15407
  async placeChunk(chunk, executor) {
15356
15408
  const changes = /* @__PURE__ */ new Map();
15357
15409
  try {
15358
- const previous = [...this._chunks.entries()].filter(([, section]) => section.x === chunk.x && section.z === chunk.z);
15359
- for (const [key, section] of previous) {
15360
- this._chunks.delete(key);
15361
- await section.clear(async (positions) => {
15362
- for (const pos of positions) changes.set(pos.toArray().join(","), pos);
15363
- });
15364
- }
15410
+ await this.clearChunkColumn(chunk.x, chunk.z, changes);
15365
15411
  for (const section of chunk.sections) {
15366
15412
  const blocks = [];
15367
15413
  for (let index = 0; index < 4096; index++) {
@@ -15381,6 +15427,31 @@ var MineRenderWorld = class {
15381
15427
  await this.updateCulling([...changes.values()]);
15382
15428
  }
15383
15429
  }
15430
+ /**
15431
+ * Removes all sections at integer chunk-column coordinates, then refreshes neighboring faces and fluids.
15432
+ * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
15433
+ */
15434
+ async unloadChunkColumn(x, z) {
15435
+ if (!Number.isInteger(x) || !Number.isInteger(z)) {
15436
+ throw new RangeError("Chunk column coordinates must be integers");
15437
+ }
15438
+ await this.culling;
15439
+ const changes = /* @__PURE__ */ new Map();
15440
+ try {
15441
+ await this.clearChunkColumn(x, z, changes);
15442
+ } finally {
15443
+ await this.updateCulling([...changes.values()]);
15444
+ }
15445
+ }
15446
+ async clearChunkColumn(x, z, changes) {
15447
+ const previous = [...this._chunks.entries()].filter(([, section]) => section.x === x && section.z === z);
15448
+ for (const [key, section] of previous) {
15449
+ this._chunks.delete(key);
15450
+ await section.clear(async (positions) => {
15451
+ for (const pos of positions) changes.set(pos.toArray().join(","), pos);
15452
+ });
15453
+ }
15454
+ }
15384
15455
  async clear() {
15385
15456
  await this.culling;
15386
15457
  const chunks = [...this._chunks.values()];
@@ -15549,14 +15620,17 @@ var SectionMesh = class _SectionMesh extends import_three40.Group {
15549
15620
  for (const { atlas, x, y } of page.placements) {
15550
15621
  context.drawImage(atlas.image.canvas, x, y);
15551
15622
  }
15552
- const positions = [], normals = [], uvs = [], colors = [], indices = [];
15623
+ const positions = [], normals = [], uvs = [], uvBounds = [], colors = [], indices = [];
15553
15624
  for (const { index, template, cullMask } of page.entries) {
15554
15625
  const position = template.geometry.getAttribute("position");
15555
15626
  const normal = template.geometry.getAttribute("normal");
15556
15627
  const uv = template.geometry.getAttribute("uv");
15628
+ const bounds = template.geometry.getAttribute("uvBounds");
15557
15629
  const color = template.geometry.getAttribute("color");
15558
15630
  const sourceIndices = template.geometry.getIndex();
15559
15631
  const placement = locations.get(template.atlas).placement;
15632
+ const mapU = (u) => (placement.x + u * template.atlas.image.width) / page.width;
15633
+ const mapV = (v) => 1 - (placement.y + (1 - v) * template.atlas.image.height) / page.height;
15560
15634
  const offsetX = index % 16 * 16;
15561
15635
  const offsetY = Math.floor(index / 256) * 16;
15562
15636
  const offsetZ = Math.floor(index / 16) % 16 * 16;
@@ -15567,9 +15641,12 @@ var SectionMesh = class _SectionMesh extends import_three40.Group {
15567
15641
  const vertex = face * 4 + corner;
15568
15642
  positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
15569
15643
  normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
15570
- uvs.push(
15571
- (placement.x + uv.getX(vertex) * template.atlas.image.width) / page.width,
15572
- 1 - (placement.y + (1 - uv.getY(vertex)) * template.atlas.image.height) / page.height
15644
+ uvs.push(mapU(uv.getX(vertex)), mapV(uv.getY(vertex)));
15645
+ uvBounds.push(
15646
+ mapU(bounds?.getX(vertex) ?? 0),
15647
+ mapV(bounds?.getY(vertex) ?? 0),
15648
+ mapU(bounds?.getZ(vertex) ?? 1),
15649
+ mapV(bounds?.getW(vertex) ?? 1)
15573
15650
  );
15574
15651
  colors.push(color?.getX(vertex) ?? 1, color?.getY(vertex) ?? 1, color?.getZ(vertex) ?? 1);
15575
15652
  }
@@ -15580,11 +15657,12 @@ var SectionMesh = class _SectionMesh extends import_three40.Group {
15580
15657
  geometry.setAttribute("position", new import_three40.Float32BufferAttribute(positions, 3));
15581
15658
  geometry.setAttribute("normal", new import_three40.Float32BufferAttribute(normals, 3));
15582
15659
  geometry.setAttribute("uv", new import_three40.Float32BufferAttribute(uvs, 2));
15660
+ geometry.setAttribute("uvBounds", new import_three40.Float32BufferAttribute(uvBounds, 4));
15583
15661
  geometry.setAttribute("color", new import_three40.Float32BufferAttribute(colors, 3));
15584
15662
  geometry.setIndex(indices);
15585
15663
  geometry.computeBoundingBox();
15586
15664
  geometry.computeBoundingSphere();
15587
- const material = Materials.createShadedCanvasMaterial(canvas, false);
15665
+ const material = Materials.createShadedCanvasMaterial(canvas, false, false, true);
15588
15666
  material.vertexColors = true;
15589
15667
  const texture = material.uniforms?.map?.value ?? material.map;
15590
15668
  const mesh = new import_three40.Mesh(geometry, material);