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.
@@ -292,7 +292,7 @@ declare class Materials {
292
292
  */
293
293
  static createEntityCanvasMaterial(canvas: HTMLCanvasElement, mode?: EntityRenderMode, tint?: ColorRepresentation): MeshBasicMaterial;
294
294
  static createGuiCanvasMaterial(canvas: HTMLCanvasElement): MeshBasicMaterial;
295
- static createShadedCanvasMaterial(canvas: HTMLCanvasElement, transparent?: boolean, shade?: boolean): Material;
295
+ static createShadedCanvasMaterial(canvas: HTMLCanvasElement, transparent?: boolean, shade?: boolean, boundUvs?: boolean): Material;
296
296
  static getImage(key: MaterialKey): Material;
297
297
  static needsUpdate(mat: Material): void;
298
298
  }
@@ -694,6 +694,8 @@ declare class UVMapper {
694
694
  static setCubeUvs(geometry: BoxGeometry, originalTextureSize: DoubleArray, actualTextureSize: DoubleArray, faces: ModelFaces): void;
695
695
  static facesToUvArray(faces: ModelFaces, originalTextureSize: DoubleArray, actualTextureSize: DoubleArray): number[];
696
696
  static facesToUv(faces: ModelFaces, originalTextureSize: DoubleArray, actualTextureSize: DoubleArray): Float32BufferAttribute;
697
+ /** Bounds texture sampling to texel centres without changing cropped face UVs. */
698
+ static setAtlasUvBounds(geometry: BoxGeometry, faces: ModelFaces, atlas: TextureAtlas): void;
697
699
  static lockUvs(geometry: BoxGeometry, faces: ModelFaces, atlas: TextureAtlas, rotation: Euler): void;
698
700
  static getAtlas(model: Model): Promise<Maybe<TextureAtlas>>;
699
701
  static fillMissingTextureKeys<T>(textures: ModelTextures, target: {
@@ -2516,6 +2518,12 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
2516
2518
  private placeBlocks;
2517
2519
  /** Replaces one chunk column's blocks. Entity NBT remains available on the parsed chunk. */
2518
2520
  placeChunk(chunk: AnvilChunk, executor?: BatchedExecutor): Promise<void>;
2521
+ /**
2522
+ * Removes all sections at integer chunk-column coordinates, then refreshes neighboring faces and fluids.
2523
+ * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
2524
+ */
2525
+ unloadChunkColumn(x: number, z: number): Promise<void>;
2526
+ private clearChunkColumn;
2519
2527
  clear(): Promise<void>;
2520
2528
  private updateCulling;
2521
2529
  private getOrCreateChunkAt;
@@ -292,7 +292,7 @@ declare class Materials {
292
292
  */
293
293
  static createEntityCanvasMaterial(canvas: HTMLCanvasElement, mode?: EntityRenderMode, tint?: ColorRepresentation): MeshBasicMaterial;
294
294
  static createGuiCanvasMaterial(canvas: HTMLCanvasElement): MeshBasicMaterial;
295
- static createShadedCanvasMaterial(canvas: HTMLCanvasElement, transparent?: boolean, shade?: boolean): Material;
295
+ static createShadedCanvasMaterial(canvas: HTMLCanvasElement, transparent?: boolean, shade?: boolean, boundUvs?: boolean): Material;
296
296
  static getImage(key: MaterialKey): Material;
297
297
  static needsUpdate(mat: Material): void;
298
298
  }
@@ -694,6 +694,8 @@ declare class UVMapper {
694
694
  static setCubeUvs(geometry: BoxGeometry, originalTextureSize: DoubleArray, actualTextureSize: DoubleArray, faces: ModelFaces): void;
695
695
  static facesToUvArray(faces: ModelFaces, originalTextureSize: DoubleArray, actualTextureSize: DoubleArray): number[];
696
696
  static facesToUv(faces: ModelFaces, originalTextureSize: DoubleArray, actualTextureSize: DoubleArray): Float32BufferAttribute;
697
+ /** Bounds texture sampling to texel centres without changing cropped face UVs. */
698
+ static setAtlasUvBounds(geometry: BoxGeometry, faces: ModelFaces, atlas: TextureAtlas): void;
697
699
  static lockUvs(geometry: BoxGeometry, faces: ModelFaces, atlas: TextureAtlas, rotation: Euler): void;
698
700
  static getAtlas(model: Model): Promise<Maybe<TextureAtlas>>;
699
701
  static fillMissingTextureKeys<T>(textures: ModelTextures, target: {
@@ -2516,6 +2518,12 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
2516
2518
  private placeBlocks;
2517
2519
  /** Replaces one chunk column's blocks. Entity NBT remains available on the parsed chunk. */
2518
2520
  placeChunk(chunk: AnvilChunk, executor?: BatchedExecutor): Promise<void>;
2521
+ /**
2522
+ * Removes all sections at integer chunk-column coordinates, then refreshes neighboring faces and fluids.
2523
+ * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
2524
+ */
2525
+ unloadChunkColumn(x: number, z: number): Promise<void>;
2526
+ private clearChunkColumn;
2519
2527
  clear(): Promise<void>;
2520
2528
  private updateCulling;
2521
2529
  private getOrCreateChunkAt;
@@ -1032,7 +1032,7 @@ var init_Materials = __esm({
1032
1032
  };
1033
1033
  return material;
1034
1034
  }
1035
- static createShadedCanvasMaterial(canvas, transparent = false, shade = false) {
1035
+ static createShadedCanvasMaterial(canvas, transparent = false, shade = false, boundUvs = false) {
1036
1036
  const vertShader = `
1037
1037
  uniform bool SHADE;
1038
1038
 
@@ -1042,6 +1042,10 @@ var init_Materials = __esm({
1042
1042
  #else
1043
1043
  varying vec2 vUv;
1044
1044
  #endif
1045
+ #ifdef BOUND_UVS
1046
+ attribute vec4 uvBounds;
1047
+ flat out vec4 vUvBounds;
1048
+ #endif
1045
1049
  varying vec3 vTint;
1046
1050
  varying float light;
1047
1051
  varying float lift;
@@ -1077,13 +1081,17 @@ var init_Materials = __esm({
1077
1081
  }
1078
1082
 
1079
1083
  vUv = uv;
1084
+ #ifdef BOUND_UVS
1085
+ vUvBounds = uvBounds;
1086
+ #endif
1080
1087
  vTint = color;
1081
1088
 
1089
+ vec4 mvPosition = vec4(position, 1.0);
1082
1090
  #ifdef USE_INSTANCING
1083
- gl_Position = projectionMatrix * viewMatrix * modelMatrix * instanceMatrix * vec4(position, 1.0);
1084
- #else
1085
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
1091
+ mvPosition = instanceMatrix * mvPosition;
1086
1092
  #endif
1093
+ mvPosition = modelViewMatrix * mvPosition;
1094
+ gl_Position = projectionMatrix * mvPosition;
1087
1095
 
1088
1096
  }
1089
1097
  `;
@@ -1103,13 +1111,22 @@ var init_Materials = __esm({
1103
1111
  #else
1104
1112
  varying vec2 vUv;
1105
1113
  #endif
1114
+ #ifdef BOUND_UVS
1115
+ flat in vec4 vUvBounds;
1116
+ #endif
1106
1117
  varying vec3 vTint;
1107
1118
  varying float light;
1108
1119
  varying float lift;
1109
1120
 
1110
1121
  void main(void)
1111
1122
  {
1112
- vec4 color = texture2D(map, vUv);
1123
+ #ifdef BOUND_UVS
1124
+ // Keep edge samples inside their texture even without centroid interpolation.
1125
+ vec2 mapUv = clamp(vUv, vUvBounds.xy, vUvBounds.zw);
1126
+ #else
1127
+ vec2 mapUv = vUv;
1128
+ #endif
1129
+ vec4 color = texture2D(map, mapUv);
1113
1130
  color.rgb *= vTint;
1114
1131
 
1115
1132
  if (color.a < 0.01) discard;
@@ -1130,7 +1147,8 @@ var init_Materials = __esm({
1130
1147
  }
1131
1148
  `;
1132
1149
  try {
1133
- return new import_three5.ShaderMaterial({
1150
+ const material = new import_three5.ShaderMaterial({
1151
+ defines: boundUvs ? { BOUND_UVS: true } : {},
1134
1152
  uniforms: {
1135
1153
  SHADE: { value: true },
1136
1154
  BRIGHTNESS: { value: 1 },
@@ -1148,6 +1166,8 @@ var init_Materials = __esm({
1148
1166
  side: transparent ? import_three5.DoubleSide : import_three5.FrontSide,
1149
1167
  alphaTest: 0.5
1150
1168
  });
1169
+ Object.assign(material.defaultAttributeValues, { uvBounds: [0, 0, 1, 1] });
1170
+ return material;
1151
1171
  } catch (e) {
1152
1172
  console.warn(e);
1153
1173
  }
@@ -3068,6 +3088,37 @@ var init_UVMapper = __esm({
3068
3088
  static facesToUv(faces, originalTextureSize, actualTextureSize) {
3069
3089
  return new import_three9.Float32BufferAttribute(this.facesToUvArray(faces, originalTextureSize, actualTextureSize), 2);
3070
3090
  }
3091
+ /** Bounds texture sampling to texel centres without changing cropped face UVs. */
3092
+ static setAtlasUvBounds(geometry, faces, atlas) {
3093
+ const uv = geometry.getAttribute("uv");
3094
+ const bounds = new Float32Array(uv.count * 4);
3095
+ for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
3096
+ const texture = faces[faceName]?.texture?.substring(1);
3097
+ const position = texture && atlas.positions[texture];
3098
+ const size = texture && atlas.sizes[texture];
3099
+ let minU = Infinity, minV = Infinity, maxU = -Infinity, maxV = -Infinity;
3100
+ if (position && size) {
3101
+ minU = position[0] / atlas.image.width;
3102
+ maxU = (position[0] + size[0]) / atlas.image.width;
3103
+ minV = 1 - (position[1] + size[1]) / atlas.image.height;
3104
+ maxV = 1 - position[1] / atlas.image.height;
3105
+ } else {
3106
+ for (let corner = 0; corner < 4; corner++) {
3107
+ const index = faceIndex * 4 + corner;
3108
+ minU = Math.min(minU, uv.getX(index));
3109
+ minV = Math.min(minV, uv.getY(index));
3110
+ maxU = Math.max(maxU, uv.getX(index));
3111
+ maxV = Math.max(maxV, uv.getY(index));
3112
+ }
3113
+ }
3114
+ const insetU = Math.min(0.5 / atlas.image.width, (maxU - minU) / 2);
3115
+ const insetV = Math.min(0.5 / atlas.image.height, (maxV - minV) / 2);
3116
+ for (let corner = 0; corner < 4; corner++) {
3117
+ bounds.set([minU + insetU, minV + insetV, maxU - insetU, maxV - insetV], (faceIndex * 4 + corner) * 4);
3118
+ }
3119
+ }
3120
+ geometry.setAttribute("uvBounds", new import_three9.Float32BufferAttribute(bounds, 4));
3121
+ }
3071
3122
  static lockUvs(geometry, faces, atlas, rotation) {
3072
3123
  const uv = geometry.getAttribute("uv");
3073
3124
  for (let faceIndex = 0; faceIndex < CUBE_FACES.length; faceIndex++) {
@@ -5470,6 +5521,7 @@ var init_ModelObject = __esm({
5470
5521
  if (this.options.uvLockRotation) {
5471
5522
  UVMapper.lockUvs(elGeo, el.faces, this.atlas, new import_three25.Euler(...this.options.uvLockRotation));
5472
5523
  }
5524
+ UVMapper.setAtlasUvBounds(elGeo, el.faces, this.atlas);
5473
5525
  if (this.options.tints) {
5474
5526
  const colors = new Float32Array(elGeo.getAttribute("position").count * 3).fill(1);
5475
5527
  for (const [faceIndex, faceName] of CUBE_FACES.entries()) {
@@ -5535,7 +5587,7 @@ var init_ModelObject = __esm({
5535
5587
  }
5536
5588
  applyTextures() {
5537
5589
  if (this.atlas) {
5538
- const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false);
5590
+ const mat = Materials.createShadedCanvasMaterial(this.atlas.image.canvas, this.atlas.hasTransparency, false, true);
5539
5591
  this.atlasMaterial = mat;
5540
5592
  this.atlasTexture = mat.uniforms?.map?.value ?? mat.map;
5541
5593
  this.iterateAllMeshes((mesh) => {
@@ -15411,13 +15463,7 @@ var MineRenderWorld = class {
15411
15463
  async placeChunk(chunk, executor) {
15412
15464
  const changes = /* @__PURE__ */ new Map();
15413
15465
  try {
15414
- const previous = [...this._chunks.entries()].filter(([, section]) => section.x === chunk.x && section.z === chunk.z);
15415
- for (const [key, section] of previous) {
15416
- this._chunks.delete(key);
15417
- await section.clear(async (positions) => {
15418
- for (const pos of positions) changes.set(pos.toArray().join(","), pos);
15419
- });
15420
- }
15466
+ await this.clearChunkColumn(chunk.x, chunk.z, changes);
15421
15467
  for (const section of chunk.sections) {
15422
15468
  const blocks = [];
15423
15469
  for (let index = 0; index < 4096; index++) {
@@ -15437,6 +15483,31 @@ var MineRenderWorld = class {
15437
15483
  await this.updateCulling([...changes.values()]);
15438
15484
  }
15439
15485
  }
15486
+ /**
15487
+ * Removes all sections at integer chunk-column coordinates, then refreshes neighboring faces and fluids.
15488
+ * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
15489
+ */
15490
+ async unloadChunkColumn(x, z) {
15491
+ if (!Number.isInteger(x) || !Number.isInteger(z)) {
15492
+ throw new RangeError("Chunk column coordinates must be integers");
15493
+ }
15494
+ await this.culling;
15495
+ const changes = /* @__PURE__ */ new Map();
15496
+ try {
15497
+ await this.clearChunkColumn(x, z, changes);
15498
+ } finally {
15499
+ await this.updateCulling([...changes.values()]);
15500
+ }
15501
+ }
15502
+ async clearChunkColumn(x, z, changes) {
15503
+ const previous = [...this._chunks.entries()].filter(([, section]) => section.x === x && section.z === z);
15504
+ for (const [key, section] of previous) {
15505
+ this._chunks.delete(key);
15506
+ await section.clear(async (positions) => {
15507
+ for (const pos of positions) changes.set(pos.toArray().join(","), pos);
15508
+ });
15509
+ }
15510
+ }
15440
15511
  async clear() {
15441
15512
  await this.culling;
15442
15513
  const chunks = [...this._chunks.values()];
@@ -15605,14 +15676,17 @@ var SectionMesh = class _SectionMesh extends import_three40.Group {
15605
15676
  for (const { atlas, x, y } of page.placements) {
15606
15677
  context.drawImage(atlas.image.canvas, x, y);
15607
15678
  }
15608
- const positions = [], normals = [], uvs = [], colors = [], indices = [];
15679
+ const positions = [], normals = [], uvs = [], uvBounds = [], colors = [], indices = [];
15609
15680
  for (const { index, template, cullMask } of page.entries) {
15610
15681
  const position = template.geometry.getAttribute("position");
15611
15682
  const normal = template.geometry.getAttribute("normal");
15612
15683
  const uv = template.geometry.getAttribute("uv");
15684
+ const bounds = template.geometry.getAttribute("uvBounds");
15613
15685
  const color = template.geometry.getAttribute("color");
15614
15686
  const sourceIndices = template.geometry.getIndex();
15615
15687
  const placement = locations.get(template.atlas).placement;
15688
+ const mapU = (u) => (placement.x + u * template.atlas.image.width) / page.width;
15689
+ const mapV = (v) => 1 - (placement.y + (1 - v) * template.atlas.image.height) / page.height;
15616
15690
  const offsetX = index % 16 * 16;
15617
15691
  const offsetY = Math.floor(index / 256) * 16;
15618
15692
  const offsetZ = Math.floor(index / 16) % 16 * 16;
@@ -15623,9 +15697,12 @@ var SectionMesh = class _SectionMesh extends import_three40.Group {
15623
15697
  const vertex = face * 4 + corner;
15624
15698
  positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
15625
15699
  normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
15626
- uvs.push(
15627
- (placement.x + uv.getX(vertex) * template.atlas.image.width) / page.width,
15628
- 1 - (placement.y + (1 - uv.getY(vertex)) * template.atlas.image.height) / page.height
15700
+ uvs.push(mapU(uv.getX(vertex)), mapV(uv.getY(vertex)));
15701
+ uvBounds.push(
15702
+ mapU(bounds?.getX(vertex) ?? 0),
15703
+ mapV(bounds?.getY(vertex) ?? 0),
15704
+ mapU(bounds?.getZ(vertex) ?? 1),
15705
+ mapV(bounds?.getW(vertex) ?? 1)
15629
15706
  );
15630
15707
  colors.push(color?.getX(vertex) ?? 1, color?.getY(vertex) ?? 1, color?.getZ(vertex) ?? 1);
15631
15708
  }
@@ -15636,11 +15713,12 @@ var SectionMesh = class _SectionMesh extends import_three40.Group {
15636
15713
  geometry.setAttribute("position", new import_three40.Float32BufferAttribute(positions, 3));
15637
15714
  geometry.setAttribute("normal", new import_three40.Float32BufferAttribute(normals, 3));
15638
15715
  geometry.setAttribute("uv", new import_three40.Float32BufferAttribute(uvs, 2));
15716
+ geometry.setAttribute("uvBounds", new import_three40.Float32BufferAttribute(uvBounds, 4));
15639
15717
  geometry.setAttribute("color", new import_three40.Float32BufferAttribute(colors, 3));
15640
15718
  geometry.setIndex(indices);
15641
15719
  geometry.computeBoundingBox();
15642
15720
  geometry.computeBoundingSphere();
15643
- const material = Materials.createShadedCanvasMaterial(canvas, false);
15721
+ const material = Materials.createShadedCanvasMaterial(canvas, false, false, true);
15644
15722
  material.vertexColors = true;
15645
15723
  const texture = material.uniforms?.map?.value ?? material.map;
15646
15724
  const mesh = new import_three40.Mesh(geometry, material);