From 558e525b18cef6800557c026e9acce4cc963fd55 Mon Sep 17 00:00:00 2001 From: Mark7625 <72366279+Mark7625@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:21:12 +0000 Subject: [PATCH 1/3] Added Gap Filling --- build.gradle | 1 - .../rs117/hd/renderer/zone/SceneManager.java | 8 + .../rs117/hd/renderer/zone/SceneUploader.java | 363 ++++++++++++++++++ 3 files changed, 371 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7244c66e81..830f716d9b 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,6 @@ plugins { } repositories { - mavenLocal() maven { url = 'https://repo.runelite.net' } diff --git a/src/main/java/rs117/hd/renderer/zone/SceneManager.java b/src/main/java/rs117/hd/renderer/zone/SceneManager.java index 6370b07e71..d0f3b65d70 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneManager.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneManager.java @@ -170,6 +170,12 @@ public void update() { if (!generateSceneDataTask.isDone()) generateSceneDataTask.waitForCompletion(); + + root.sceneContext.fillGaps = config.fillGapsInTerrain(); + for (var sub : subs) + if (sub != null && sub.sceneContext != null) + sub.sceneContext.fillGaps = config.fillGapsInTerrain(); + generateSceneDataTask.queue(); root.invalidate(); @@ -410,6 +416,7 @@ public synchronized void loadScene(WorldView worldView, Scene scene) { Scene prev = client.getTopLevelWorldView().getScene(); nextSceneContext.enableAreaHiding = nextSceneContext.sceneBase != null && config.hideUnrelatedAreas(); + nextSceneContext.fillGaps = config.fillGapsInTerrain(); if (nextSceneContext.intersects(areaManager.getArea("PLAYER_OWNED_HOUSE"))) { nextSceneContext.isInHouse = true; @@ -704,6 +711,7 @@ private void loadSubScene(WorldView worldView, Scene scene) { } var sceneContext = new ZoneSceneContext(client, worldView, scene, plugin.getExpandedMapLoadingChunks(), null); + sceneContext.fillGaps = config.fillGapsInTerrain(); proceduralGenerator.generateSceneData(sceneContext); final WorldViewContext ctx = new WorldViewContext(worldView, sceneContext, uboWorldViews); diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index badfc9bb67..8f669a706d 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -44,6 +44,7 @@ import rs117.hd.scene.model_overrides.UvType; import rs117.hd.scene.tile_overrides.TileOverride; import rs117.hd.scene.water_types.WaterType; +import rs117.hd.scene.areas.Area; import rs117.hd.utils.HDUtils; import rs117.hd.utils.ModelHash; import rs117.hd.utils.buffer.GpuIntBuffer; @@ -65,6 +66,11 @@ public class SceneUploader implements AutoCloseable { public static final int MAX_VERTEX_COUNT = 6500; private static final int[] UP_NORMAL = { 0, -1, 0 }; + private static final int[] SOUTH_NORMAL = { 0, 0, 1 }; + private static final int[] EAST_NORMAL = { -1, 0, 0 }; + private static final int[] NORTH_NORMAL = { 0, 0, -1 }; + private static final int[] WEST_NORMAL = { 1, 0, 0 }; + private static final int GAP_DEPTH_OFFSET = 600; private final int[] EMPTY_NORMALS = new int[9]; public static final float[] GEOMETRY_UVS = { @@ -88,6 +94,9 @@ public class SceneUploader implements AutoCloseable { MAX_BRIGHTNESS_LOOKUP_TABLE[i] = (int) (127 - 72 * Math.pow(i / 7f, .05)); } + @Inject + private Client client; + @Inject private RenderCallbackManager renderCallbackManager; @@ -190,6 +199,110 @@ public void estimateZoneSize(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) } } } + + if (ctx.fillGaps) + estimateZoneGapTiles(ctx, zone, mzx, mzz); + } + + private void estimateZoneGapTiles(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) { + if (ctx.sceneBase == null) + return; + + var area = ctx.currentArea; + if (area != null && !area.fillGaps) + return; + + int sceneMin = -ctx.expandedMapLoadingChunks * CHUNK_SIZE; + int sceneMax = net.runelite.api.Constants.SCENE_SIZE + ctx.expandedMapLoadingChunks * CHUNK_SIZE; + int baseExX = ctx.sceneBase[0]; + int baseExY = ctx.sceneBase[1]; + int basePlane = ctx.sceneBase[2]; + int[] regions = client.getMapRegions(); + + for (int xoff = 0; xoff < 8; ++xoff) { + for (int zoff = 0; zoff < 8; ++zoff) { + int tileExX = (mzx << 3) + xoff; + int tileExY = (mzz << 3) + zoff; + int tileX = tileExX - ctx.sceneOffset; + int tileY = tileExY - ctx.sceneOffset; + Tile tile = tiles[0][tileExX][tileExY]; + + SceneTilePaint paint; + SceneTileModel model = null; + if (tile != null) { + paint = tile.getSceneTilePaint(); + model = tile.getSceneTileModel(); + + if (model == null) { + boolean hasTilePaint = paint != null && paint.getNeColor() != HIDDEN_HSL; + if (!hasTilePaint) { + tile = tile.getBridge(); + if (tile != null) { + paint = tile.getSceneTilePaint(); + model = tile.getSceneTileModel(); + hasTilePaint = paint != null && paint.getNeColor() != HIDDEN_HSL; + } + } + + if (hasTilePaint) + continue; + } else { + // Count hidden faces for model gap filling (box of black at bottom) + ctx.sceneToWorld(tileX, tileY, 0, worldPos); + boolean fillGapsModel = + tileX > sceneMin && + tileY > sceneMin && + tileX < sceneMax - 1 && + tileY < sceneMax - 1 && + Area.OVERWORLD.containsPoint(worldPos); + if (fillGapsModel) { + int tileRegionID = HDUtils.worldToRegionID(worldPos); + fillGapsModel = false; + for (int region : regions) { + if (region == tileRegionID) { + fillGapsModel = true; + break; + } + } + } + if (fillGapsModel && (area == null || area.containsPoint(baseExX + tileExX, baseExY + tileExY, basePlane))) { + int[] faceColorA = model.getTriangleColorA(); + for (int f = 0; f < faceColorA.length; f++) { + if (faceColorA[f] == HIDDEN_HSL) { + zone.sizeO += 1; + zone.sizeF += 1; + } + } + } + continue; + } + } + + ctx.sceneToWorld(tileX, tileY, 0, worldPos); + boolean fillGaps = + tileX > sceneMin && + tileY > sceneMin && + tileX < sceneMax - 1 && + tileY < sceneMax - 1 && + Area.OVERWORLD.containsPoint(worldPos); + + if (fillGaps) { + int tileRegionID = HDUtils.worldToRegionID(worldPos); + fillGaps = false; + for (int region : regions) { + if (region == tileRegionID) { + fillGaps = true; + break; + } + } + } + + if (fillGaps && (area == null || area.containsPoint(baseExX + tileExX, baseExY + tileExY, basePlane))) { + zone.sizeO += 10; + zone.sizeF += 10; + } + } + } } public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws InterruptedException { @@ -217,6 +330,8 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws this.level = z; if (z == 0) { + if (ctx.fillGaps && vb != null) + uploadZoneGapTiles(ctx, zone, mzx, mzz, vb, fb); uploadZoneLevel(ctx, zone, mzx, mzz, 0, false, roofIds, vb, ab, fb); uploadZoneLevel(ctx, zone, mzx, mzz, 0, true, roofIds, vb, ab, fb); uploadZoneLevel(ctx, zone, mzx, mzz, 1, true, roofIds, vb, ab, fb); @@ -273,6 +388,254 @@ private void uploadZoneLevel( uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, 0, visbelow, vb, ab, fb); } + private static boolean isHorizontalFace(int x0, int y0, int z0, int x1, int y1, int z1, int x2, int y2, int z2) { + int ex = x1 - x0, ey = y1 - y0, ez = z1 - z0; + int fx = x2 - x0, fy = y2 - y0, fz = z2 - z0; + int nx = ey * fz - ez * fy, ny = ez * fx - ex * fz, nz = ex * fy - ey * fx; + int any = ny < 0 ? -ny : ny, anx = nx < 0 ? -nx : nx, anz = nz < 0 ? -nz : nz; + return any > anx + anz; + } + + private void putGapSide(GpuIntBuffer vb, GpuIntBuffer fb, + int x0, int z0, int bot0, int top0, + int x1, int z1, int bot1, int top1, + int[] normal, int color, int materialData, int terrainData + ) { + int snx = normal[0], sny = normal[2], snz = normal[1]; + int fi = fb.putFace(color, color, color, materialData, materialData, materialData, terrainData, terrainData, terrainData); + vb.putVertex(x0, bot0, z0, 0, 0, 0, snx, sny, snz, fi); + vb.putVertex(x1, top1, z1, 0, 0, 0, snx, sny, snz, fi); + vb.putVertex(x1, bot1, z1, 0, 0, 0, snx, sny, snz, fi); + fi = fb.putFace(color, color, color, materialData, materialData, materialData, terrainData, terrainData, terrainData); + vb.putVertex(x0, bot0, z0, 0, 0, 0, snx, sny, snz, fi); + vb.putVertex(x0, top0, z0, 0, 0, 0, snx, sny, snz, fi); + vb.putVertex(x1, top1, z1, 0, 0, 0, snx, sny, snz, fi); + } + + private boolean isInFillGapsRegion(ZoneSceneContext ctx, int tileX, int tileY, + int sceneMin, int sceneMax, int[] regions) { + if (tileX <= sceneMin || tileY <= sceneMin || tileX >= sceneMax - 1 || tileY >= sceneMax - 1) + return false; + ctx.sceneToWorld(tileX, tileY, 0, worldPos); + if (!Area.OVERWORLD.containsPoint(worldPos)) + return false; + int tileRegionID = HDUtils.worldToRegionID(worldPos); + for (int r : regions) { + if (r == tileRegionID) + return true; + } + return false; + } + + private boolean isFlatGapTile( + ZoneSceneContext ctx, Tile[][][] tiles, + int tileExX, int tileExY, + int sceneMin, int sceneMax, + int[] regions, Area area, + int baseExX, int baseExY, int basePlane + ) { + if (tileExX < 0 || tileExY < 0 || tileExX >= EXTENDED_SCENE_SIZE || tileExY >= EXTENDED_SCENE_SIZE) + return false; + int tileX = tileExX - ctx.sceneOffset; + int tileY = tileExY - ctx.sceneOffset; + Tile tile = tiles[0][tileExX][tileExY]; + if (tile != null) { + if (tile.getSceneTileModel() != null) + return false; + boolean hasPaint = tile.getSceneTilePaint() != null && tile.getSceneTilePaint().getNeColor() != HIDDEN_HSL; + if (!hasPaint && tile.getBridge() != null) { + Tile bridge = tile.getBridge(); + hasPaint = bridge.getSceneTilePaint() != null && bridge.getSceneTilePaint().getNeColor() != HIDDEN_HSL; + } + if (hasPaint) + return false; + } + return isInFillGapsRegion(ctx, tileX, tileY, sceneMin, sceneMax, regions) + && (area == null || area.containsPoint(baseExX + tileExX, baseExY + tileExY, basePlane)); + } + + private void uploadZoneGapTiles(ZoneSceneContext ctx, Zone zone, int mzx, int mzz, GpuIntBuffer vb, GpuIntBuffer fb) { + if (!ctx.fillGaps || ctx.sceneBase == null || (ctx.currentArea != null && !ctx.currentArea.fillGaps)) + return; + + var area = ctx.currentArea; + int sceneMin = -ctx.expandedMapLoadingChunks * CHUNK_SIZE; + int sceneMax = net.runelite.api.Constants.SCENE_SIZE + ctx.expandedMapLoadingChunks * CHUNK_SIZE; + int baseExX = ctx.sceneBase[0]; + int baseExY = ctx.sceneBase[1]; + int basePlane = ctx.sceneBase[2]; + int[] regions = client.getMapRegions(); + Material blackMaterial = materialManager.getMaterial("BLACK"); + int materialData = blackMaterial.packMaterialData(ModelOverride.NONE, UvType.GEOMETRY, false); + this.basex = (mzx - (ctx.sceneOffset >> 3)) << 10; + this.basez = (mzz - (ctx.sceneOffset >> 3)) << 10; + + for (int xoff = 0; xoff < 8; ++xoff) { + for (int zoff = 0; zoff < 8; ++zoff) { + int tileExX = (mzx << 3) + xoff; + int tileExY = (mzz << 3) + zoff; + if (area != null && !area.containsPoint(baseExX + tileExX, baseExY + tileExY, basePlane)) + continue; + + int tileX = tileExX - ctx.sceneOffset; + int tileY = tileExY - ctx.sceneOffset; + Tile tile = tiles[0][tileExX][tileExY]; + + SceneTilePaint paint; + SceneTileModel model = null; + if (tile != null) { + paint = tile.getSceneTilePaint(); + model = tile.getSceneTileModel(); + + if (model == null) { + boolean hasTilePaint = paint != null && paint.getNeColor() != HIDDEN_HSL; + if (!hasTilePaint) { + tile = tile.getBridge(); + if (tile != null) { + paint = tile.getSceneTilePaint(); + model = tile.getSceneTileModel(); + hasTilePaint = paint != null && paint.getNeColor() != HIDDEN_HSL; + } + } + + if (hasTilePaint) + continue; + } else { + if (isInFillGapsRegion(ctx, tileX, tileY, sceneMin, sceneMax, regions)) { + uploadGapTileModelFaces(ctx, zone, tile, model, tileExX, tileExY, vb, fb); + } + continue; + } + } + + if (!isInFillGapsRegion(ctx, tileX, tileY, sceneMin, sceneMax, regions)) + continue; + + int sceneTileX = tileExX - ctx.sceneOffset; + int sceneTileY = tileExY - ctx.sceneOffset; + int lx = sceneTileX * LOCAL_TILE_SIZE - basex; + int lz = sceneTileY * LOCAL_TILE_SIZE - basez; + int lx0 = lx, lz0 = lz; + int lx1 = lx + LOCAL_TILE_SIZE, lz1 = lz; + int lx2 = lx + LOCAL_TILE_SIZE, lz2 = lz + LOCAL_TILE_SIZE; + int lx3 = lx, lz3 = lz + LOCAL_TILE_SIZE; + + int renderLevel = tile != null ? tile.getRenderLevel() : 0; + int swBot = tileHeights[renderLevel][tileExX][tileExY] + GAP_DEPTH_OFFSET; + int seBot = tileHeights[renderLevel][tileExX + 1][tileExY] + GAP_DEPTH_OFFSET; + int neBot = tileHeights[renderLevel][tileExX + 1][tileExY + 1] + GAP_DEPTH_OFFSET; + int nwBot = tileHeights[renderLevel][tileExX][tileExY + 1] + GAP_DEPTH_OFFSET; + int swTop = tileHeights[renderLevel][tileExX][tileExY]; + int seTop = tileHeights[renderLevel][tileExX + 1][tileExY]; + int neTop = tileHeights[renderLevel][tileExX + 1][tileExY + 1]; + int nwTop = tileHeights[renderLevel][tileExX][tileExY + 1]; + + int terrainData = HDUtils.packTerrainData(true, 0, WaterType.NONE, renderLevel); + int color = 0; + + // Only draw a side when the neighbor in that direction is NOT a gap - avoids duplicate faces + boolean drawSouth = !isFlatGapTile(ctx, tiles, tileExX, tileExY - 1, sceneMin, sceneMax, regions, area, baseExX, baseExY, basePlane); + boolean drawEast = !isFlatGapTile(ctx, tiles, tileExX + 1, tileExY, sceneMin, sceneMax, regions, area, baseExX, baseExY, basePlane); + boolean drawNorth = !isFlatGapTile(ctx, tiles, tileExX, tileExY + 1, sceneMin, sceneMax, regions, area, baseExX, baseExY, basePlane); + boolean drawWest = !isFlatGapTile(ctx, tiles, tileExX - 1, tileExY, sceneMin, sceneMax, regions, area, baseExX, baseExY, basePlane); + + int u0 = UP_NORMAL[0], u1 = UP_NORMAL[2], u2 = UP_NORMAL[1]; + int fi = fb.putFace(color, color, color, materialData, materialData, materialData, terrainData, terrainData, terrainData); + vb.putVertex(lx2, neBot, lz2, 0, 0, 0, u0, u1, u2, fi); + vb.putVertex(lx3, nwBot, lz3, 0, 0, 0, u0, u1, u2, fi); + vb.putVertex(lx1, seBot, lz1, 0, 0, 0, u0, u1, u2, fi); + fi = fb.putFace(color, color, color, materialData, materialData, materialData, terrainData, terrainData, terrainData); + vb.putVertex(lx0, swBot, lz0, 0, 0, 0, u0, u1, u2, fi); + vb.putVertex(lx1, seBot, lz1, 0, 0, 0, u0, u1, u2, fi); + vb.putVertex(lx3, nwBot, lz3, 0, 0, 0, u0, u1, u2, fi); + + if (drawSouth) putGapSide(vb, fb, lx0, lz0, swBot, swTop, lx1, lz1, seBot, seTop, NORTH_NORMAL, color, materialData, terrainData); + if (drawEast) putGapSide(vb, fb, lx1, lz1, seBot, seTop, lx2, lz2, neBot, neTop, WEST_NORMAL, color, materialData, terrainData); + if (drawNorth) putGapSide(vb, fb, lx2, lz2, neBot, neTop, lx3, lz3, nwBot, nwTop, SOUTH_NORMAL, color, materialData, terrainData); + if (drawWest) putGapSide(vb, fb, lx3, lz3, nwBot, nwTop, lx0, lz0, swBot, swTop, EAST_NORMAL, color, materialData, terrainData); + } + } + } + + private void uploadGapTileModelFaces( + ZoneSceneContext ctx, + Zone zone, + Tile tile, + SceneTileModel model, + int tileExX, int tileExY, + GpuIntBuffer vb, + GpuIntBuffer fb + ) { + final int[] triangleColorA = model.getTriangleColorA(); + final int faceCount = triangleColorA.length; + + Material blackMaterial = materialManager.getMaterial("BLACK"); + int materialData = blackMaterial.packMaterialData(ModelOverride.NONE, UvType.GEOMETRY, false); + int terrainData = HDUtils.packTerrainData(true, 0, WaterType.NONE, tile.getRenderLevel()); + + int sceneTileX = tileExX - ctx.sceneOffset; + int sceneTileY = tileExY - ctx.sceneOffset; + int tileBaseX = sceneTileX * LOCAL_TILE_SIZE - basex; + int tileBaseZ = sceneTileY * LOCAL_TILE_SIZE - basez; + + for (int face = 0; face < faceCount; ++face) { + if (triangleColorA[face] != HIDDEN_HSL) + continue; + + ProceduralGenerator.faceVertexKeys(tile, face, vertices, vertexKeys); + ProceduralGenerator.faceLocalVertices(tile, face, vertices); + + int lx0 = tileBaseX + vertices[0][0]; + int ly0 = vertices[0][2] + GAP_DEPTH_OFFSET; + int lz0 = tileBaseZ + vertices[0][1]; + + int lx1 = tileBaseX + vertices[1][0]; + int ly1 = vertices[1][2] + GAP_DEPTH_OFFSET; + int lz1 = tileBaseZ + vertices[1][1]; + + int lx2 = tileBaseX + vertices[2][0]; + int ly2 = vertices[2][2] + GAP_DEPTH_OFFSET; + int lz2 = tileBaseZ + vertices[2][1]; + + if (isHorizontalFace(lx0, ly0, lz0, lx1, ly1, lz1, lx2, ly2, lz2)) + continue; + + int vertexKeyA = vertexKeys[0]; + int vertexKeyB = vertexKeys[1]; + int vertexKeyC = vertexKeys[2]; + + int[] normalsA = ctx.vertexTerrainNormals.getOrDefault(vertexKeyA, UP_NORMAL); + int[] normalsB = ctx.vertexTerrainNormals.getOrDefault(vertexKeyB, UP_NORMAL); + int[] normalsC = ctx.vertexTerrainNormals.getOrDefault(vertexKeyC, UP_NORMAL); + + int color = 0; + int texturedFaceIdx = fb.putFace( + color, color, color, + materialData, materialData, materialData, + terrainData, terrainData, terrainData + ); + + vb.putVertex( + lx0, ly0, lz0, + 0, 0, 0, + normalsA[0], normalsA[2], normalsA[1], + texturedFaceIdx + ); + vb.putVertex( + lx2, ly2, lz2, + 0, 0, 0, + normalsC[0], normalsC[2], normalsC[1], + texturedFaceIdx + ); + vb.putVertex( + lx1, ly1, lz1, + 0, 0, 0, + normalsB[0], normalsB[2], normalsB[1], + texturedFaceIdx + ); + } + } + private void uploadZoneLevelRoof( ZoneSceneContext ctx, Zone zone, From 03ec1dd0a981fd3a1888ceec4a98ee2ce9b542ab Mon Sep 17 00:00:00 2001 From: Mark7625 <72366279+Mark7625@users.noreply.github.com> Date: Tue, 10 Mar 2026 20:06:46 +0000 Subject: [PATCH 2/3] Works Mostly --- .../rs117/hd/renderer/zone/GapFiller.java | 292 ++++++++++++++++++ .../rs117/hd/renderer/zone/SceneUploader.java | 261 +--------------- 2 files changed, 302 insertions(+), 251 deletions(-) create mode 100644 src/main/java/rs117/hd/renderer/zone/GapFiller.java diff --git a/src/main/java/rs117/hd/renderer/zone/GapFiller.java b/src/main/java/rs117/hd/renderer/zone/GapFiller.java new file mode 100644 index 0000000000..7f71ccb074 --- /dev/null +++ b/src/main/java/rs117/hd/renderer/zone/GapFiller.java @@ -0,0 +1,292 @@ +package rs117.hd.renderer.zone; + +import java.util.Map; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Tile; +import rs117.hd.scene.MaterialManager; +import rs117.hd.scene.ProceduralGenerator; +import rs117.hd.scene.areas.Area; +import rs117.hd.scene.materials.Material; +import rs117.hd.scene.model_overrides.ModelOverride; +import rs117.hd.scene.model_overrides.UvType; +import rs117.hd.scene.water_types.WaterType; +import rs117.hd.utils.HDUtils; +import rs117.hd.utils.buffer.GpuIntBuffer; + +import static net.runelite.api.Constants.CHUNK_SIZE; +import static net.runelite.api.Constants.EXTENDED_SCENE_SIZE; +import static net.runelite.api.Constants.SCENE_SIZE; +import static net.runelite.api.Perspective.LOCAL_TILE_SIZE; +import static rs117.hd.utils.HDUtils.HIDDEN_HSL; + +public class GapFiller { + + private static final int[] UP_NORMAL = { 0, -1, 0 }; + private static final int[] SOUTH_NORMAL = { 0, 0, 1 }; + private static final int[] EAST_NORMAL = { -1, 0, 0 }; + private static final int[] NORTH_NORMAL = { 0, 0, -1 }; + private static final int[] WEST_NORMAL = { 1, 0, 0 }; + private static final int GAP_DEPTH_OFFSET = 1900; + private static final int GAP_EXPAND_MARGIN = 40; + private static final int ZONE_SIZE = 8; + private static final int RENDER_LEVEL = 0; + private static final int COLOR_BLACK = 0; + + @Inject + private Client client; + + @Inject + private MaterialManager materialManager; + + private final int[] worldPos = new int[3]; + + public void uploadZoneGapTiles( + ZoneSceneContext ctx, + Zone zone, + int mzx, + int mzz, + GpuIntBuffer vb, + GpuIntBuffer fb, + Tile[][][] tiles, + int[][][] tileHeights, + int basex, + int basez, + int[][] vertices, + int[] vertexKeys + ) { + if (!ctx.fillGaps || ctx.sceneBase == null || (ctx.currentArea != null && !ctx.currentArea.fillGaps)) + return; + + int sceneMin = -ctx.expandedMapLoadingChunks * CHUNK_SIZE; + int sceneMax = SCENE_SIZE + ctx.expandedMapLoadingChunks * CHUNK_SIZE; + int[] regions = client.getMapRegions(); + Area area = ctx.currentArea; + int baseExX = ctx.sceneBase[0]; + int baseExY = ctx.sceneBase[1]; + int basePlane = ctx.sceneBase[2]; + + Material black = materialManager.getMaterial("BLACK"); + int materialData = black.packMaterialData(ModelOverride.NONE, UvType.GEOMETRY, false); + int terrainData = HDUtils.packTerrainData(true, 0, WaterType.NONE, RENDER_LEVEL); + + // Pass 1: emit model hidden-face drops and mark flat-gap tiles + boolean[][] flatGap = new boolean[ZONE_SIZE][ZONE_SIZE]; + for (int xoff = 0; xoff < ZONE_SIZE; xoff++) { + for (int zoff = 0; zoff < ZONE_SIZE; zoff++) { + int tileExX = (mzx << 3) + xoff; + int tileExY = (mzz << 3) + zoff; + if (!inGapRegion(ctx, area, baseExX, baseExY, basePlane, tileExX, tileExY, sceneMin, sceneMax, regions)) + continue; + + Tile tile = tiles[0][tileExX][tileExY]; + if (tile == null) { + flatGap[xoff][zoff] = true; + continue; + } + + if (tile.getSceneTileModel() != null) { + uploadModelHiddenFaces(ctx, tile, tileExX, tileExY, vb, fb, basex, basez, materialData, terrainData, vertices, vertexKeys); + continue; + } + + if (hasVisiblePaint(tile)) + continue; + + flatGap[xoff][zoff] = true; + } + } + + // Pass 2: emit one box (bottom + 4 sides) per flat-gap tile + for (int xoff = 0; xoff < ZONE_SIZE; xoff++) { + for (int zoff = 0; zoff < ZONE_SIZE; zoff++) { + if (!flatGap[xoff][zoff]) + continue; + + int tileExX = (mzx << 3) + xoff; + int tileExY = (mzz << 3) + zoff; + int lx0 = (tileExX - ctx.sceneOffset) * LOCAL_TILE_SIZE - basex - GAP_EXPAND_MARGIN; + int lz0 = (tileExY - ctx.sceneOffset) * LOCAL_TILE_SIZE - basez - GAP_EXPAND_MARGIN; + int lx1 = (tileExX + 1 - ctx.sceneOffset) * LOCAL_TILE_SIZE - basex + GAP_EXPAND_MARGIN; + int lz1 = (tileExY + 1 - ctx.sceneOffset) * LOCAL_TILE_SIZE - basez + GAP_EXPAND_MARGIN; + + int swT = tileHeights[RENDER_LEVEL][tileExX][tileExY]; + int seT = tileHeights[RENDER_LEVEL][tileExX + 1][tileExY]; + int neT = tileHeights[RENDER_LEVEL][tileExX + 1][tileExY + 1]; + int nwT = tileHeights[RENDER_LEVEL][tileExX][tileExY + 1]; + int swB = swT + GAP_DEPTH_OFFSET; + int seB = seT + GAP_DEPTH_OFFSET; + int neB = neT + GAP_DEPTH_OFFSET; + int nwB = nwT + GAP_DEPTH_OFFSET; + + putQuadYUp(vb, fb, lx0, lz0, lx1, lz1, swB, seB, neB, nwB, materialData, terrainData); + if (isSolidGround(tiles, tileExX, tileExY - 1)) + putSide(vb, fb, lx0, lz0, swB, swT, lx1, lz0, seB, seT, SOUTH_NORMAL, materialData, terrainData); + if (isSolidGround(tiles, tileExX + 1, tileExY)) + putSide(vb, fb, lx1, lz0, seB, seT, lx1, lz1, neB, neT, WEST_NORMAL, materialData, terrainData); + if (isSolidGround(tiles, tileExX, tileExY + 1)) + putSide(vb, fb, lx1, lz1, neB, neT, lx0, lz1, nwB, nwT, NORTH_NORMAL, materialData, terrainData); + if (isSolidGround(tiles, tileExX - 1, tileExY)) + putSide(vb, fb, lx0, lz1, nwB, nwT, lx0, lz0, swB, swT, EAST_NORMAL, materialData, terrainData); + } + } + } + + /** Emit model hidden faces: bottom triangle at depth + 3 vertical drop quads. */ + private void uploadModelHiddenFaces( + ZoneSceneContext ctx, + Tile tile, + int tileExX, + int tileExY, + GpuIntBuffer vb, + GpuIntBuffer fb, + int basex, + int basez, + int materialData, + int terrainData, + int[][] vertices, + int[] vertexKeys + ) { + net.runelite.api.SceneTileModel model = tile.getSceneTileModel(); + int[] triangleColorA = model.getTriangleColorA(); + int faceCount = triangleColorA.length; + int terrainDataTile = HDUtils.packTerrainData(true, 0, WaterType.NONE, tile.getRenderLevel()); + int tileBaseX = (tileExX - ctx.sceneOffset) * LOCAL_TILE_SIZE - basex; + int tileBaseZ = (tileExY - ctx.sceneOffset) * LOCAL_TILE_SIZE - basez; + Map normals = ctx.vertexTerrainNormals; + + for (int f = 0; f < faceCount; f++) { + if (triangleColorA[f] != HIDDEN_HSL) + continue; + + ProceduralGenerator.faceVertexKeys(tile, f, vertices, vertexKeys); + ProceduralGenerator.faceLocalVertices(tile, f, vertices); + + int lx0 = tileBaseX + vertices[0][0], ly0 = vertices[0][2], lz0 = tileBaseZ + vertices[0][1]; + int lx1 = tileBaseX + vertices[1][0], ly1 = vertices[1][2], lz1 = tileBaseZ + vertices[1][1]; + int lx2 = tileBaseX + vertices[2][0], ly2 = vertices[2][2], lz2 = tileBaseZ + vertices[2][1]; + + if (isHorizontalFace(lx0, ly0, lz0, lx1, ly1, lz1, lx2, ly2, lz2)) + continue; + + int ly0B = ly0 + GAP_DEPTH_OFFSET, ly1B = ly1 + GAP_DEPTH_OFFSET, ly2B = ly2 + GAP_DEPTH_OFFSET; + int[] nA = normals.getOrDefault(vertexKeys[0], UP_NORMAL); + int[] nB = normals.getOrDefault(vertexKeys[1], UP_NORMAL); + int[] nC = normals.getOrDefault(vertexKeys[2], UP_NORMAL); + + int fi = fb.putFace(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK, materialData, materialData, materialData, terrainDataTile, terrainDataTile, terrainDataTile); + vb.putVertex(lx0, ly0B, lz0, 0, 0, 0, nA[0], nA[2], nA[1], fi); + vb.putVertex(lx2, ly2B, lz2, 0, 0, 0, nC[0], nC[2], nC[1], fi); + vb.putVertex(lx1, ly1B, lz1, 0, 0, 0, nB[0], nB[2], nB[1], fi); + + putSide(vb, fb, lx0, lz0, ly0B, ly0, lx1, lz1, ly1B, ly1, SOUTH_NORMAL, materialData, terrainDataTile); + putSide(vb, fb, lx1, lz1, ly1B, ly1, lx2, lz2, ly2B, ly2, SOUTH_NORMAL, materialData, terrainDataTile); + putSide(vb, fb, lx2, lz2, ly2B, ly2, lx0, lz0, ly0B, ly0, SOUTH_NORMAL, materialData, terrainDataTile); + } + } + + /** Horizontal quad (Y-up): two triangles for bottom face. */ + private void putQuadYUp(GpuIntBuffer vb, GpuIntBuffer fb, + int lx0, int lz0, int lx1, int lz1, + int swB, int seB, int neB, int nwB, + int materialData, int terrainData + ) { + int u0 = UP_NORMAL[0], u1 = UP_NORMAL[2], u2 = UP_NORMAL[1]; + int fi = fb.putFace(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK, materialData, materialData, materialData, terrainData, terrainData, terrainData); + vb.putVertex(lx1, neB, lz1, 0, 0, 0, u0, u1, u2, fi); + vb.putVertex(lx0, nwB, lz1, 0, 0, 0, u0, u1, u2, fi); + vb.putVertex(lx1, seB, lz0, 0, 0, 0, u0, u1, u2, fi); + fi = fb.putFace(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK, materialData, materialData, materialData, terrainData, terrainData, terrainData); + vb.putVertex(lx0, swB, lz0, 0, 0, 0, u0, u1, u2, fi); + vb.putVertex(lx1, seB, lz0, 0, 0, 0, u0, u1, u2, fi); + vb.putVertex(lx0, nwB, lz1, 0, 0, 0, u0, u1, u2, fi); + } + + /** Vertical quad from (x0,z0) to (x1,z1), bottom-to-top. */ + private void putSide(GpuIntBuffer vb, GpuIntBuffer fb, + int x0, int z0, int bot0, int top0, + int x1, int z1, int bot1, int top1, + int[] outwardNormal, + int materialData, int terrainData + ) { + int dx = x1 - x0, dz = z1 - z0, dy = top0 - bot0; + int nx = -dz * dy, nz = dx * dy; + int snx, sny, snz; + if (nx == 0 && nz == 0) { + snx = -outwardNormal[0]; + sny = -outwardNormal[2]; + snz = -outwardNormal[1]; + } else { + int dot = nx * outwardNormal[0] + nz * outwardNormal[2]; + if (dot > 0) { + nx = -nx; + nz = -nz; + } + snx = nx; + sny = nz; + snz = 0; + } + int fi = fb.putFace(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK, materialData, materialData, materialData, terrainData, terrainData, terrainData); + vb.putVertex(x0, bot0, z0, 0, 0, 0, snx, sny, snz, fi); + vb.putVertex(x1, top1, z1, 0, 0, 0, snx, sny, snz, fi); + vb.putVertex(x1, bot1, z1, 0, 0, 0, snx, sny, snz, fi); + fi = fb.putFace(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK, materialData, materialData, materialData, terrainData, terrainData, terrainData); + vb.putVertex(x0, bot0, z0, 0, 0, 0, snx, sny, snz, fi); + vb.putVertex(x0, top0, z0, 0, 0, 0, snx, sny, snz, fi); + vb.putVertex(x1, top1, z1, 0, 0, 0, snx, sny, snz, fi); + } + + private boolean inGapRegion(ZoneSceneContext ctx, Area area, int baseExX, int baseExY, int basePlane, + int tileExX, int tileExY, int sceneMin, int sceneMax, int[] regions) { + int tileX = tileExX - ctx.sceneOffset; + int tileY = tileExY - ctx.sceneOffset; + if (tileX <= sceneMin || tileY <= sceneMin || tileX >= sceneMax - 1 || tileY >= sceneMax - 1) + return false; + if (area != null && !area.containsPoint(baseExX + tileExX, baseExY + tileExY, basePlane)) + return false; + ctx.sceneToWorld(tileX, tileY, 0, worldPos); + if (!Area.OVERWORLD.containsPoint(worldPos)) + return false; + int regionId = HDUtils.worldToRegionID(worldPos); + for (int r : regions) { + if (r == regionId) + return true; + } + return false; + } + + private boolean hasVisiblePaint(Tile tile) { + if (tile == null) + return false; + net.runelite.api.SceneTilePaint p = tile.getSceneTilePaint(); + if (p != null && p.getNeColor() != HIDDEN_HSL) + return true; + Tile bridge = tile.getBridge(); + if (bridge != null) { + p = bridge.getSceneTilePaint(); + if (p != null && p.getNeColor() != HIDDEN_HSL) + return true; + } + return false; + } + + private boolean isSolidGround(Tile[][][] tiles, int tileExX, int tileExY) { + if (tileExX < 0 || tileExY < 0 || tileExX >= EXTENDED_SCENE_SIZE || tileExY >= EXTENDED_SCENE_SIZE) + return false; + Tile t = tiles[0][tileExX][tileExY]; + if (t == null) + return false; + if (t.getSceneTileModel() != null) + return true; + net.runelite.api.SceneTilePaint p = t.getSceneTilePaint(); + return p != null && p.getNeColor() != HIDDEN_HSL; + } + + private static boolean isHorizontalFace(int x0, int y0, int z0, int x1, int y1, int z1, int x2, int y2, int z2) { + int ex = x1 - x0, ey = y1 - y0, ez = z1 - z0; + int fx = x2 - x0, fy = y2 - y0, fz = z2 - z0; + int nx = ey * fz - ez * fy, ny = ez * fx - ex * fz, nz = ex * fy - ey * fx; + int ay = ny < 0 ? -ny : ny, ax = nx < 0 ? -nx : nx, az = nz < 0 ? -nz : nz; + return ay > ax + az; + } +} diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index aee3740bad..7f64f11d81 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -24,7 +24,9 @@ */ package rs117.hd.renderer.zone; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; @@ -70,7 +72,6 @@ public class SceneUploader implements AutoCloseable { private static final int[] EAST_NORMAL = { -1, 0, 0 }; private static final int[] NORTH_NORMAL = { 0, 0, -1 }; private static final int[] WEST_NORMAL = { 1, 0, 0 }; - private static final int GAP_DEPTH_OFFSET = 600; private final int[] EMPTY_NORMALS = new int[9]; public static final float[] GEOMETRY_UVS = { @@ -115,6 +116,9 @@ public class SceneUploader implements AutoCloseable { @Inject private ModelOverrideManager modelOverrideManager; + @Inject + private GapFiller gapFiller; + @Inject public ProceduralGenerator proceduralGenerator; @@ -330,8 +334,11 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws this.level = z; if (z == 0) { - if (ctx.fillGaps && vb != null) - uploadZoneGapTiles(ctx, zone, mzx, mzz, vb, fb); + if (ctx.fillGaps && vb != null) { + this.basex = (mzx - (ctx.sceneOffset >> 3)) << 10; + this.basez = (mzz - (ctx.sceneOffset >> 3)) << 10; + gapFiller.uploadZoneGapTiles(ctx, zone, mzx, mzz, vb, fb, tiles, tileHeights, basex, basez, vertices, vertexKeys); + } uploadZoneLevel(ctx, zone, mzx, mzz, 0, false, roofIds, vb, ab, fb); uploadZoneLevel(ctx, zone, mzx, mzz, 0, true, roofIds, vb, ab, fb); uploadZoneLevel(ctx, zone, mzx, mzz, 1, true, roofIds, vb, ab, fb); @@ -388,254 +395,6 @@ private void uploadZoneLevel( uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, 0, visbelow, vb, ab, fb); } - private static boolean isHorizontalFace(int x0, int y0, int z0, int x1, int y1, int z1, int x2, int y2, int z2) { - int ex = x1 - x0, ey = y1 - y0, ez = z1 - z0; - int fx = x2 - x0, fy = y2 - y0, fz = z2 - z0; - int nx = ey * fz - ez * fy, ny = ez * fx - ex * fz, nz = ex * fy - ey * fx; - int any = ny < 0 ? -ny : ny, anx = nx < 0 ? -nx : nx, anz = nz < 0 ? -nz : nz; - return any > anx + anz; - } - - private void putGapSide(GpuIntBuffer vb, GpuIntBuffer fb, - int x0, int z0, int bot0, int top0, - int x1, int z1, int bot1, int top1, - int[] normal, int color, int materialData, int terrainData - ) { - int snx = normal[0], sny = normal[2], snz = normal[1]; - int fi = fb.putFace(color, color, color, materialData, materialData, materialData, terrainData, terrainData, terrainData); - vb.putVertex(x0, bot0, z0, 0, 0, 0, snx, sny, snz, fi); - vb.putVertex(x1, top1, z1, 0, 0, 0, snx, sny, snz, fi); - vb.putVertex(x1, bot1, z1, 0, 0, 0, snx, sny, snz, fi); - fi = fb.putFace(color, color, color, materialData, materialData, materialData, terrainData, terrainData, terrainData); - vb.putVertex(x0, bot0, z0, 0, 0, 0, snx, sny, snz, fi); - vb.putVertex(x0, top0, z0, 0, 0, 0, snx, sny, snz, fi); - vb.putVertex(x1, top1, z1, 0, 0, 0, snx, sny, snz, fi); - } - - private boolean isInFillGapsRegion(ZoneSceneContext ctx, int tileX, int tileY, - int sceneMin, int sceneMax, int[] regions) { - if (tileX <= sceneMin || tileY <= sceneMin || tileX >= sceneMax - 1 || tileY >= sceneMax - 1) - return false; - ctx.sceneToWorld(tileX, tileY, 0, worldPos); - if (!Area.OVERWORLD.containsPoint(worldPos)) - return false; - int tileRegionID = HDUtils.worldToRegionID(worldPos); - for (int r : regions) { - if (r == tileRegionID) - return true; - } - return false; - } - - private boolean isFlatGapTile( - ZoneSceneContext ctx, Tile[][][] tiles, - int tileExX, int tileExY, - int sceneMin, int sceneMax, - int[] regions, Area area, - int baseExX, int baseExY, int basePlane - ) { - if (tileExX < 0 || tileExY < 0 || tileExX >= EXTENDED_SCENE_SIZE || tileExY >= EXTENDED_SCENE_SIZE) - return false; - int tileX = tileExX - ctx.sceneOffset; - int tileY = tileExY - ctx.sceneOffset; - Tile tile = tiles[0][tileExX][tileExY]; - if (tile != null) { - if (tile.getSceneTileModel() != null) - return false; - boolean hasPaint = tile.getSceneTilePaint() != null && tile.getSceneTilePaint().getNeColor() != HIDDEN_HSL; - if (!hasPaint && tile.getBridge() != null) { - Tile bridge = tile.getBridge(); - hasPaint = bridge.getSceneTilePaint() != null && bridge.getSceneTilePaint().getNeColor() != HIDDEN_HSL; - } - if (hasPaint) - return false; - } - return isInFillGapsRegion(ctx, tileX, tileY, sceneMin, sceneMax, regions) - && (area == null || area.containsPoint(baseExX + tileExX, baseExY + tileExY, basePlane)); - } - - private void uploadZoneGapTiles(ZoneSceneContext ctx, Zone zone, int mzx, int mzz, GpuIntBuffer vb, GpuIntBuffer fb) { - if (!ctx.fillGaps || ctx.sceneBase == null || (ctx.currentArea != null && !ctx.currentArea.fillGaps)) - return; - - var area = ctx.currentArea; - int sceneMin = -ctx.expandedMapLoadingChunks * CHUNK_SIZE; - int sceneMax = net.runelite.api.Constants.SCENE_SIZE + ctx.expandedMapLoadingChunks * CHUNK_SIZE; - int baseExX = ctx.sceneBase[0]; - int baseExY = ctx.sceneBase[1]; - int basePlane = ctx.sceneBase[2]; - int[] regions = client.getMapRegions(); - Material blackMaterial = materialManager.getMaterial("BLACK"); - int materialData = blackMaterial.packMaterialData(ModelOverride.NONE, UvType.GEOMETRY, false); - this.basex = (mzx - (ctx.sceneOffset >> 3)) << 10; - this.basez = (mzz - (ctx.sceneOffset >> 3)) << 10; - - for (int xoff = 0; xoff < 8; ++xoff) { - for (int zoff = 0; zoff < 8; ++zoff) { - int tileExX = (mzx << 3) + xoff; - int tileExY = (mzz << 3) + zoff; - if (area != null && !area.containsPoint(baseExX + tileExX, baseExY + tileExY, basePlane)) - continue; - - int tileX = tileExX - ctx.sceneOffset; - int tileY = tileExY - ctx.sceneOffset; - Tile tile = tiles[0][tileExX][tileExY]; - - SceneTilePaint paint; - SceneTileModel model = null; - if (tile != null) { - paint = tile.getSceneTilePaint(); - model = tile.getSceneTileModel(); - - if (model == null) { - boolean hasTilePaint = paint != null && paint.getNeColor() != HIDDEN_HSL; - if (!hasTilePaint) { - tile = tile.getBridge(); - if (tile != null) { - paint = tile.getSceneTilePaint(); - model = tile.getSceneTileModel(); - hasTilePaint = paint != null && paint.getNeColor() != HIDDEN_HSL; - } - } - - if (hasTilePaint) - continue; - } else { - if (isInFillGapsRegion(ctx, tileX, tileY, sceneMin, sceneMax, regions)) { - uploadGapTileModelFaces(ctx, zone, tile, model, tileExX, tileExY, vb, fb); - } - continue; - } - } - - if (!isInFillGapsRegion(ctx, tileX, tileY, sceneMin, sceneMax, regions)) - continue; - - int sceneTileX = tileExX - ctx.sceneOffset; - int sceneTileY = tileExY - ctx.sceneOffset; - int lx = sceneTileX * LOCAL_TILE_SIZE - basex; - int lz = sceneTileY * LOCAL_TILE_SIZE - basez; - int lx0 = lx, lz0 = lz; - int lx1 = lx + LOCAL_TILE_SIZE, lz1 = lz; - int lx2 = lx + LOCAL_TILE_SIZE, lz2 = lz + LOCAL_TILE_SIZE; - int lx3 = lx, lz3 = lz + LOCAL_TILE_SIZE; - - int renderLevel = tile != null ? tile.getRenderLevel() : 0; - int swBot = tileHeights[renderLevel][tileExX][tileExY] + GAP_DEPTH_OFFSET; - int seBot = tileHeights[renderLevel][tileExX + 1][tileExY] + GAP_DEPTH_OFFSET; - int neBot = tileHeights[renderLevel][tileExX + 1][tileExY + 1] + GAP_DEPTH_OFFSET; - int nwBot = tileHeights[renderLevel][tileExX][tileExY + 1] + GAP_DEPTH_OFFSET; - int swTop = tileHeights[renderLevel][tileExX][tileExY]; - int seTop = tileHeights[renderLevel][tileExX + 1][tileExY]; - int neTop = tileHeights[renderLevel][tileExX + 1][tileExY + 1]; - int nwTop = tileHeights[renderLevel][tileExX][tileExY + 1]; - - int terrainData = HDUtils.packTerrainData(true, 0, WaterType.NONE, renderLevel); - int color = 0; - - // Only draw a side when the neighbor in that direction is NOT a gap - avoids duplicate faces - boolean drawSouth = !isFlatGapTile(ctx, tiles, tileExX, tileExY - 1, sceneMin, sceneMax, regions, area, baseExX, baseExY, basePlane); - boolean drawEast = !isFlatGapTile(ctx, tiles, tileExX + 1, tileExY, sceneMin, sceneMax, regions, area, baseExX, baseExY, basePlane); - boolean drawNorth = !isFlatGapTile(ctx, tiles, tileExX, tileExY + 1, sceneMin, sceneMax, regions, area, baseExX, baseExY, basePlane); - boolean drawWest = !isFlatGapTile(ctx, tiles, tileExX - 1, tileExY, sceneMin, sceneMax, regions, area, baseExX, baseExY, basePlane); - - int u0 = UP_NORMAL[0], u1 = UP_NORMAL[2], u2 = UP_NORMAL[1]; - int fi = fb.putFace(color, color, color, materialData, materialData, materialData, terrainData, terrainData, terrainData); - vb.putVertex(lx2, neBot, lz2, 0, 0, 0, u0, u1, u2, fi); - vb.putVertex(lx3, nwBot, lz3, 0, 0, 0, u0, u1, u2, fi); - vb.putVertex(lx1, seBot, lz1, 0, 0, 0, u0, u1, u2, fi); - fi = fb.putFace(color, color, color, materialData, materialData, materialData, terrainData, terrainData, terrainData); - vb.putVertex(lx0, swBot, lz0, 0, 0, 0, u0, u1, u2, fi); - vb.putVertex(lx1, seBot, lz1, 0, 0, 0, u0, u1, u2, fi); - vb.putVertex(lx3, nwBot, lz3, 0, 0, 0, u0, u1, u2, fi); - - if (drawSouth) putGapSide(vb, fb, lx0, lz0, swBot, swTop, lx1, lz1, seBot, seTop, NORTH_NORMAL, color, materialData, terrainData); - if (drawEast) putGapSide(vb, fb, lx1, lz1, seBot, seTop, lx2, lz2, neBot, neTop, WEST_NORMAL, color, materialData, terrainData); - if (drawNorth) putGapSide(vb, fb, lx2, lz2, neBot, neTop, lx3, lz3, nwBot, nwTop, SOUTH_NORMAL, color, materialData, terrainData); - if (drawWest) putGapSide(vb, fb, lx3, lz3, nwBot, nwTop, lx0, lz0, swBot, swTop, EAST_NORMAL, color, materialData, terrainData); - } - } - } - - private void uploadGapTileModelFaces( - ZoneSceneContext ctx, - Zone zone, - Tile tile, - SceneTileModel model, - int tileExX, int tileExY, - GpuIntBuffer vb, - GpuIntBuffer fb - ) { - final int[] triangleColorA = model.getTriangleColorA(); - final int faceCount = triangleColorA.length; - - Material blackMaterial = materialManager.getMaterial("BLACK"); - int materialData = blackMaterial.packMaterialData(ModelOverride.NONE, UvType.GEOMETRY, false); - int terrainData = HDUtils.packTerrainData(true, 0, WaterType.NONE, tile.getRenderLevel()); - - int sceneTileX = tileExX - ctx.sceneOffset; - int sceneTileY = tileExY - ctx.sceneOffset; - int tileBaseX = sceneTileX * LOCAL_TILE_SIZE - basex; - int tileBaseZ = sceneTileY * LOCAL_TILE_SIZE - basez; - - for (int face = 0; face < faceCount; ++face) { - if (triangleColorA[face] != HIDDEN_HSL) - continue; - - ProceduralGenerator.faceVertexKeys(tile, face, vertices, vertexKeys); - ProceduralGenerator.faceLocalVertices(tile, face, vertices); - - int lx0 = tileBaseX + vertices[0][0]; - int ly0 = vertices[0][2] + GAP_DEPTH_OFFSET; - int lz0 = tileBaseZ + vertices[0][1]; - - int lx1 = tileBaseX + vertices[1][0]; - int ly1 = vertices[1][2] + GAP_DEPTH_OFFSET; - int lz1 = tileBaseZ + vertices[1][1]; - - int lx2 = tileBaseX + vertices[2][0]; - int ly2 = vertices[2][2] + GAP_DEPTH_OFFSET; - int lz2 = tileBaseZ + vertices[2][1]; - - if (isHorizontalFace(lx0, ly0, lz0, lx1, ly1, lz1, lx2, ly2, lz2)) - continue; - - int vertexKeyA = vertexKeys[0]; - int vertexKeyB = vertexKeys[1]; - int vertexKeyC = vertexKeys[2]; - - int[] normalsA = ctx.vertexTerrainNormals.getOrDefault(vertexKeyA, UP_NORMAL); - int[] normalsB = ctx.vertexTerrainNormals.getOrDefault(vertexKeyB, UP_NORMAL); - int[] normalsC = ctx.vertexTerrainNormals.getOrDefault(vertexKeyC, UP_NORMAL); - - int color = 0; - int texturedFaceIdx = fb.putFace( - color, color, color, - materialData, materialData, materialData, - terrainData, terrainData, terrainData - ); - - vb.putVertex( - lx0, ly0, lz0, - 0, 0, 0, - normalsA[0], normalsA[2], normalsA[1], - texturedFaceIdx - ); - vb.putVertex( - lx2, ly2, lz2, - 0, 0, 0, - normalsC[0], normalsC[2], normalsC[1], - texturedFaceIdx - ); - vb.putVertex( - lx1, ly1, lz1, - 0, 0, 0, - normalsB[0], normalsB[2], normalsB[1], - texturedFaceIdx - ); - } - } - private void uploadZoneLevelRoof( ZoneSceneContext ctx, Zone zone, From f7081bee23039e0bf94232d94bd2f02f64bfeb2b Mon Sep 17 00:00:00 2001 From: Mark7625 <72366279+Mark7625@users.noreply.github.com> Date: Tue, 10 Mar 2026 20:07:47 +0000 Subject: [PATCH 3/3] Revert "Works Mostly" This reverts commit 03ec1dd0a981fd3a1888ceec4a98ee2ce9b542ab. --- .../rs117/hd/renderer/zone/GapFiller.java | 292 ------------------ .../rs117/hd/renderer/zone/SceneUploader.java | 261 +++++++++++++++- 2 files changed, 251 insertions(+), 302 deletions(-) delete mode 100644 src/main/java/rs117/hd/renderer/zone/GapFiller.java diff --git a/src/main/java/rs117/hd/renderer/zone/GapFiller.java b/src/main/java/rs117/hd/renderer/zone/GapFiller.java deleted file mode 100644 index 7f71ccb074..0000000000 --- a/src/main/java/rs117/hd/renderer/zone/GapFiller.java +++ /dev/null @@ -1,292 +0,0 @@ -package rs117.hd.renderer.zone; - -import java.util.Map; -import javax.inject.Inject; -import net.runelite.api.Client; -import net.runelite.api.Tile; -import rs117.hd.scene.MaterialManager; -import rs117.hd.scene.ProceduralGenerator; -import rs117.hd.scene.areas.Area; -import rs117.hd.scene.materials.Material; -import rs117.hd.scene.model_overrides.ModelOverride; -import rs117.hd.scene.model_overrides.UvType; -import rs117.hd.scene.water_types.WaterType; -import rs117.hd.utils.HDUtils; -import rs117.hd.utils.buffer.GpuIntBuffer; - -import static net.runelite.api.Constants.CHUNK_SIZE; -import static net.runelite.api.Constants.EXTENDED_SCENE_SIZE; -import static net.runelite.api.Constants.SCENE_SIZE; -import static net.runelite.api.Perspective.LOCAL_TILE_SIZE; -import static rs117.hd.utils.HDUtils.HIDDEN_HSL; - -public class GapFiller { - - private static final int[] UP_NORMAL = { 0, -1, 0 }; - private static final int[] SOUTH_NORMAL = { 0, 0, 1 }; - private static final int[] EAST_NORMAL = { -1, 0, 0 }; - private static final int[] NORTH_NORMAL = { 0, 0, -1 }; - private static final int[] WEST_NORMAL = { 1, 0, 0 }; - private static final int GAP_DEPTH_OFFSET = 1900; - private static final int GAP_EXPAND_MARGIN = 40; - private static final int ZONE_SIZE = 8; - private static final int RENDER_LEVEL = 0; - private static final int COLOR_BLACK = 0; - - @Inject - private Client client; - - @Inject - private MaterialManager materialManager; - - private final int[] worldPos = new int[3]; - - public void uploadZoneGapTiles( - ZoneSceneContext ctx, - Zone zone, - int mzx, - int mzz, - GpuIntBuffer vb, - GpuIntBuffer fb, - Tile[][][] tiles, - int[][][] tileHeights, - int basex, - int basez, - int[][] vertices, - int[] vertexKeys - ) { - if (!ctx.fillGaps || ctx.sceneBase == null || (ctx.currentArea != null && !ctx.currentArea.fillGaps)) - return; - - int sceneMin = -ctx.expandedMapLoadingChunks * CHUNK_SIZE; - int sceneMax = SCENE_SIZE + ctx.expandedMapLoadingChunks * CHUNK_SIZE; - int[] regions = client.getMapRegions(); - Area area = ctx.currentArea; - int baseExX = ctx.sceneBase[0]; - int baseExY = ctx.sceneBase[1]; - int basePlane = ctx.sceneBase[2]; - - Material black = materialManager.getMaterial("BLACK"); - int materialData = black.packMaterialData(ModelOverride.NONE, UvType.GEOMETRY, false); - int terrainData = HDUtils.packTerrainData(true, 0, WaterType.NONE, RENDER_LEVEL); - - // Pass 1: emit model hidden-face drops and mark flat-gap tiles - boolean[][] flatGap = new boolean[ZONE_SIZE][ZONE_SIZE]; - for (int xoff = 0; xoff < ZONE_SIZE; xoff++) { - for (int zoff = 0; zoff < ZONE_SIZE; zoff++) { - int tileExX = (mzx << 3) + xoff; - int tileExY = (mzz << 3) + zoff; - if (!inGapRegion(ctx, area, baseExX, baseExY, basePlane, tileExX, tileExY, sceneMin, sceneMax, regions)) - continue; - - Tile tile = tiles[0][tileExX][tileExY]; - if (tile == null) { - flatGap[xoff][zoff] = true; - continue; - } - - if (tile.getSceneTileModel() != null) { - uploadModelHiddenFaces(ctx, tile, tileExX, tileExY, vb, fb, basex, basez, materialData, terrainData, vertices, vertexKeys); - continue; - } - - if (hasVisiblePaint(tile)) - continue; - - flatGap[xoff][zoff] = true; - } - } - - // Pass 2: emit one box (bottom + 4 sides) per flat-gap tile - for (int xoff = 0; xoff < ZONE_SIZE; xoff++) { - for (int zoff = 0; zoff < ZONE_SIZE; zoff++) { - if (!flatGap[xoff][zoff]) - continue; - - int tileExX = (mzx << 3) + xoff; - int tileExY = (mzz << 3) + zoff; - int lx0 = (tileExX - ctx.sceneOffset) * LOCAL_TILE_SIZE - basex - GAP_EXPAND_MARGIN; - int lz0 = (tileExY - ctx.sceneOffset) * LOCAL_TILE_SIZE - basez - GAP_EXPAND_MARGIN; - int lx1 = (tileExX + 1 - ctx.sceneOffset) * LOCAL_TILE_SIZE - basex + GAP_EXPAND_MARGIN; - int lz1 = (tileExY + 1 - ctx.sceneOffset) * LOCAL_TILE_SIZE - basez + GAP_EXPAND_MARGIN; - - int swT = tileHeights[RENDER_LEVEL][tileExX][tileExY]; - int seT = tileHeights[RENDER_LEVEL][tileExX + 1][tileExY]; - int neT = tileHeights[RENDER_LEVEL][tileExX + 1][tileExY + 1]; - int nwT = tileHeights[RENDER_LEVEL][tileExX][tileExY + 1]; - int swB = swT + GAP_DEPTH_OFFSET; - int seB = seT + GAP_DEPTH_OFFSET; - int neB = neT + GAP_DEPTH_OFFSET; - int nwB = nwT + GAP_DEPTH_OFFSET; - - putQuadYUp(vb, fb, lx0, lz0, lx1, lz1, swB, seB, neB, nwB, materialData, terrainData); - if (isSolidGround(tiles, tileExX, tileExY - 1)) - putSide(vb, fb, lx0, lz0, swB, swT, lx1, lz0, seB, seT, SOUTH_NORMAL, materialData, terrainData); - if (isSolidGround(tiles, tileExX + 1, tileExY)) - putSide(vb, fb, lx1, lz0, seB, seT, lx1, lz1, neB, neT, WEST_NORMAL, materialData, terrainData); - if (isSolidGround(tiles, tileExX, tileExY + 1)) - putSide(vb, fb, lx1, lz1, neB, neT, lx0, lz1, nwB, nwT, NORTH_NORMAL, materialData, terrainData); - if (isSolidGround(tiles, tileExX - 1, tileExY)) - putSide(vb, fb, lx0, lz1, nwB, nwT, lx0, lz0, swB, swT, EAST_NORMAL, materialData, terrainData); - } - } - } - - /** Emit model hidden faces: bottom triangle at depth + 3 vertical drop quads. */ - private void uploadModelHiddenFaces( - ZoneSceneContext ctx, - Tile tile, - int tileExX, - int tileExY, - GpuIntBuffer vb, - GpuIntBuffer fb, - int basex, - int basez, - int materialData, - int terrainData, - int[][] vertices, - int[] vertexKeys - ) { - net.runelite.api.SceneTileModel model = tile.getSceneTileModel(); - int[] triangleColorA = model.getTriangleColorA(); - int faceCount = triangleColorA.length; - int terrainDataTile = HDUtils.packTerrainData(true, 0, WaterType.NONE, tile.getRenderLevel()); - int tileBaseX = (tileExX - ctx.sceneOffset) * LOCAL_TILE_SIZE - basex; - int tileBaseZ = (tileExY - ctx.sceneOffset) * LOCAL_TILE_SIZE - basez; - Map normals = ctx.vertexTerrainNormals; - - for (int f = 0; f < faceCount; f++) { - if (triangleColorA[f] != HIDDEN_HSL) - continue; - - ProceduralGenerator.faceVertexKeys(tile, f, vertices, vertexKeys); - ProceduralGenerator.faceLocalVertices(tile, f, vertices); - - int lx0 = tileBaseX + vertices[0][0], ly0 = vertices[0][2], lz0 = tileBaseZ + vertices[0][1]; - int lx1 = tileBaseX + vertices[1][0], ly1 = vertices[1][2], lz1 = tileBaseZ + vertices[1][1]; - int lx2 = tileBaseX + vertices[2][0], ly2 = vertices[2][2], lz2 = tileBaseZ + vertices[2][1]; - - if (isHorizontalFace(lx0, ly0, lz0, lx1, ly1, lz1, lx2, ly2, lz2)) - continue; - - int ly0B = ly0 + GAP_DEPTH_OFFSET, ly1B = ly1 + GAP_DEPTH_OFFSET, ly2B = ly2 + GAP_DEPTH_OFFSET; - int[] nA = normals.getOrDefault(vertexKeys[0], UP_NORMAL); - int[] nB = normals.getOrDefault(vertexKeys[1], UP_NORMAL); - int[] nC = normals.getOrDefault(vertexKeys[2], UP_NORMAL); - - int fi = fb.putFace(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK, materialData, materialData, materialData, terrainDataTile, terrainDataTile, terrainDataTile); - vb.putVertex(lx0, ly0B, lz0, 0, 0, 0, nA[0], nA[2], nA[1], fi); - vb.putVertex(lx2, ly2B, lz2, 0, 0, 0, nC[0], nC[2], nC[1], fi); - vb.putVertex(lx1, ly1B, lz1, 0, 0, 0, nB[0], nB[2], nB[1], fi); - - putSide(vb, fb, lx0, lz0, ly0B, ly0, lx1, lz1, ly1B, ly1, SOUTH_NORMAL, materialData, terrainDataTile); - putSide(vb, fb, lx1, lz1, ly1B, ly1, lx2, lz2, ly2B, ly2, SOUTH_NORMAL, materialData, terrainDataTile); - putSide(vb, fb, lx2, lz2, ly2B, ly2, lx0, lz0, ly0B, ly0, SOUTH_NORMAL, materialData, terrainDataTile); - } - } - - /** Horizontal quad (Y-up): two triangles for bottom face. */ - private void putQuadYUp(GpuIntBuffer vb, GpuIntBuffer fb, - int lx0, int lz0, int lx1, int lz1, - int swB, int seB, int neB, int nwB, - int materialData, int terrainData - ) { - int u0 = UP_NORMAL[0], u1 = UP_NORMAL[2], u2 = UP_NORMAL[1]; - int fi = fb.putFace(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK, materialData, materialData, materialData, terrainData, terrainData, terrainData); - vb.putVertex(lx1, neB, lz1, 0, 0, 0, u0, u1, u2, fi); - vb.putVertex(lx0, nwB, lz1, 0, 0, 0, u0, u1, u2, fi); - vb.putVertex(lx1, seB, lz0, 0, 0, 0, u0, u1, u2, fi); - fi = fb.putFace(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK, materialData, materialData, materialData, terrainData, terrainData, terrainData); - vb.putVertex(lx0, swB, lz0, 0, 0, 0, u0, u1, u2, fi); - vb.putVertex(lx1, seB, lz0, 0, 0, 0, u0, u1, u2, fi); - vb.putVertex(lx0, nwB, lz1, 0, 0, 0, u0, u1, u2, fi); - } - - /** Vertical quad from (x0,z0) to (x1,z1), bottom-to-top. */ - private void putSide(GpuIntBuffer vb, GpuIntBuffer fb, - int x0, int z0, int bot0, int top0, - int x1, int z1, int bot1, int top1, - int[] outwardNormal, - int materialData, int terrainData - ) { - int dx = x1 - x0, dz = z1 - z0, dy = top0 - bot0; - int nx = -dz * dy, nz = dx * dy; - int snx, sny, snz; - if (nx == 0 && nz == 0) { - snx = -outwardNormal[0]; - sny = -outwardNormal[2]; - snz = -outwardNormal[1]; - } else { - int dot = nx * outwardNormal[0] + nz * outwardNormal[2]; - if (dot > 0) { - nx = -nx; - nz = -nz; - } - snx = nx; - sny = nz; - snz = 0; - } - int fi = fb.putFace(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK, materialData, materialData, materialData, terrainData, terrainData, terrainData); - vb.putVertex(x0, bot0, z0, 0, 0, 0, snx, sny, snz, fi); - vb.putVertex(x1, top1, z1, 0, 0, 0, snx, sny, snz, fi); - vb.putVertex(x1, bot1, z1, 0, 0, 0, snx, sny, snz, fi); - fi = fb.putFace(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK, materialData, materialData, materialData, terrainData, terrainData, terrainData); - vb.putVertex(x0, bot0, z0, 0, 0, 0, snx, sny, snz, fi); - vb.putVertex(x0, top0, z0, 0, 0, 0, snx, sny, snz, fi); - vb.putVertex(x1, top1, z1, 0, 0, 0, snx, sny, snz, fi); - } - - private boolean inGapRegion(ZoneSceneContext ctx, Area area, int baseExX, int baseExY, int basePlane, - int tileExX, int tileExY, int sceneMin, int sceneMax, int[] regions) { - int tileX = tileExX - ctx.sceneOffset; - int tileY = tileExY - ctx.sceneOffset; - if (tileX <= sceneMin || tileY <= sceneMin || tileX >= sceneMax - 1 || tileY >= sceneMax - 1) - return false; - if (area != null && !area.containsPoint(baseExX + tileExX, baseExY + tileExY, basePlane)) - return false; - ctx.sceneToWorld(tileX, tileY, 0, worldPos); - if (!Area.OVERWORLD.containsPoint(worldPos)) - return false; - int regionId = HDUtils.worldToRegionID(worldPos); - for (int r : regions) { - if (r == regionId) - return true; - } - return false; - } - - private boolean hasVisiblePaint(Tile tile) { - if (tile == null) - return false; - net.runelite.api.SceneTilePaint p = tile.getSceneTilePaint(); - if (p != null && p.getNeColor() != HIDDEN_HSL) - return true; - Tile bridge = tile.getBridge(); - if (bridge != null) { - p = bridge.getSceneTilePaint(); - if (p != null && p.getNeColor() != HIDDEN_HSL) - return true; - } - return false; - } - - private boolean isSolidGround(Tile[][][] tiles, int tileExX, int tileExY) { - if (tileExX < 0 || tileExY < 0 || tileExX >= EXTENDED_SCENE_SIZE || tileExY >= EXTENDED_SCENE_SIZE) - return false; - Tile t = tiles[0][tileExX][tileExY]; - if (t == null) - return false; - if (t.getSceneTileModel() != null) - return true; - net.runelite.api.SceneTilePaint p = t.getSceneTilePaint(); - return p != null && p.getNeColor() != HIDDEN_HSL; - } - - private static boolean isHorizontalFace(int x0, int y0, int z0, int x1, int y1, int z1, int x2, int y2, int z2) { - int ex = x1 - x0, ey = y1 - y0, ez = z1 - z0; - int fx = x2 - x0, fy = y2 - y0, fz = z2 - z0; - int nx = ey * fz - ez * fy, ny = ez * fx - ex * fz, nz = ex * fy - ey * fx; - int ay = ny < 0 ? -ny : ny, ax = nx < 0 ? -nx : nx, az = nz < 0 ? -nz : nz; - return ay > ax + az; - } -} diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index 7f64f11d81..aee3740bad 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -24,9 +24,7 @@ */ package rs117.hd.renderer.zone; -import java.util.HashMap; import java.util.HashSet; -import java.util.Map; import java.util.Set; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; @@ -72,6 +70,7 @@ public class SceneUploader implements AutoCloseable { private static final int[] EAST_NORMAL = { -1, 0, 0 }; private static final int[] NORTH_NORMAL = { 0, 0, -1 }; private static final int[] WEST_NORMAL = { 1, 0, 0 }; + private static final int GAP_DEPTH_OFFSET = 600; private final int[] EMPTY_NORMALS = new int[9]; public static final float[] GEOMETRY_UVS = { @@ -116,9 +115,6 @@ public class SceneUploader implements AutoCloseable { @Inject private ModelOverrideManager modelOverrideManager; - @Inject - private GapFiller gapFiller; - @Inject public ProceduralGenerator proceduralGenerator; @@ -334,11 +330,8 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws this.level = z; if (z == 0) { - if (ctx.fillGaps && vb != null) { - this.basex = (mzx - (ctx.sceneOffset >> 3)) << 10; - this.basez = (mzz - (ctx.sceneOffset >> 3)) << 10; - gapFiller.uploadZoneGapTiles(ctx, zone, mzx, mzz, vb, fb, tiles, tileHeights, basex, basez, vertices, vertexKeys); - } + if (ctx.fillGaps && vb != null) + uploadZoneGapTiles(ctx, zone, mzx, mzz, vb, fb); uploadZoneLevel(ctx, zone, mzx, mzz, 0, false, roofIds, vb, ab, fb); uploadZoneLevel(ctx, zone, mzx, mzz, 0, true, roofIds, vb, ab, fb); uploadZoneLevel(ctx, zone, mzx, mzz, 1, true, roofIds, vb, ab, fb); @@ -395,6 +388,254 @@ private void uploadZoneLevel( uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, 0, visbelow, vb, ab, fb); } + private static boolean isHorizontalFace(int x0, int y0, int z0, int x1, int y1, int z1, int x2, int y2, int z2) { + int ex = x1 - x0, ey = y1 - y0, ez = z1 - z0; + int fx = x2 - x0, fy = y2 - y0, fz = z2 - z0; + int nx = ey * fz - ez * fy, ny = ez * fx - ex * fz, nz = ex * fy - ey * fx; + int any = ny < 0 ? -ny : ny, anx = nx < 0 ? -nx : nx, anz = nz < 0 ? -nz : nz; + return any > anx + anz; + } + + private void putGapSide(GpuIntBuffer vb, GpuIntBuffer fb, + int x0, int z0, int bot0, int top0, + int x1, int z1, int bot1, int top1, + int[] normal, int color, int materialData, int terrainData + ) { + int snx = normal[0], sny = normal[2], snz = normal[1]; + int fi = fb.putFace(color, color, color, materialData, materialData, materialData, terrainData, terrainData, terrainData); + vb.putVertex(x0, bot0, z0, 0, 0, 0, snx, sny, snz, fi); + vb.putVertex(x1, top1, z1, 0, 0, 0, snx, sny, snz, fi); + vb.putVertex(x1, bot1, z1, 0, 0, 0, snx, sny, snz, fi); + fi = fb.putFace(color, color, color, materialData, materialData, materialData, terrainData, terrainData, terrainData); + vb.putVertex(x0, bot0, z0, 0, 0, 0, snx, sny, snz, fi); + vb.putVertex(x0, top0, z0, 0, 0, 0, snx, sny, snz, fi); + vb.putVertex(x1, top1, z1, 0, 0, 0, snx, sny, snz, fi); + } + + private boolean isInFillGapsRegion(ZoneSceneContext ctx, int tileX, int tileY, + int sceneMin, int sceneMax, int[] regions) { + if (tileX <= sceneMin || tileY <= sceneMin || tileX >= sceneMax - 1 || tileY >= sceneMax - 1) + return false; + ctx.sceneToWorld(tileX, tileY, 0, worldPos); + if (!Area.OVERWORLD.containsPoint(worldPos)) + return false; + int tileRegionID = HDUtils.worldToRegionID(worldPos); + for (int r : regions) { + if (r == tileRegionID) + return true; + } + return false; + } + + private boolean isFlatGapTile( + ZoneSceneContext ctx, Tile[][][] tiles, + int tileExX, int tileExY, + int sceneMin, int sceneMax, + int[] regions, Area area, + int baseExX, int baseExY, int basePlane + ) { + if (tileExX < 0 || tileExY < 0 || tileExX >= EXTENDED_SCENE_SIZE || tileExY >= EXTENDED_SCENE_SIZE) + return false; + int tileX = tileExX - ctx.sceneOffset; + int tileY = tileExY - ctx.sceneOffset; + Tile tile = tiles[0][tileExX][tileExY]; + if (tile != null) { + if (tile.getSceneTileModel() != null) + return false; + boolean hasPaint = tile.getSceneTilePaint() != null && tile.getSceneTilePaint().getNeColor() != HIDDEN_HSL; + if (!hasPaint && tile.getBridge() != null) { + Tile bridge = tile.getBridge(); + hasPaint = bridge.getSceneTilePaint() != null && bridge.getSceneTilePaint().getNeColor() != HIDDEN_HSL; + } + if (hasPaint) + return false; + } + return isInFillGapsRegion(ctx, tileX, tileY, sceneMin, sceneMax, regions) + && (area == null || area.containsPoint(baseExX + tileExX, baseExY + tileExY, basePlane)); + } + + private void uploadZoneGapTiles(ZoneSceneContext ctx, Zone zone, int mzx, int mzz, GpuIntBuffer vb, GpuIntBuffer fb) { + if (!ctx.fillGaps || ctx.sceneBase == null || (ctx.currentArea != null && !ctx.currentArea.fillGaps)) + return; + + var area = ctx.currentArea; + int sceneMin = -ctx.expandedMapLoadingChunks * CHUNK_SIZE; + int sceneMax = net.runelite.api.Constants.SCENE_SIZE + ctx.expandedMapLoadingChunks * CHUNK_SIZE; + int baseExX = ctx.sceneBase[0]; + int baseExY = ctx.sceneBase[1]; + int basePlane = ctx.sceneBase[2]; + int[] regions = client.getMapRegions(); + Material blackMaterial = materialManager.getMaterial("BLACK"); + int materialData = blackMaterial.packMaterialData(ModelOverride.NONE, UvType.GEOMETRY, false); + this.basex = (mzx - (ctx.sceneOffset >> 3)) << 10; + this.basez = (mzz - (ctx.sceneOffset >> 3)) << 10; + + for (int xoff = 0; xoff < 8; ++xoff) { + for (int zoff = 0; zoff < 8; ++zoff) { + int tileExX = (mzx << 3) + xoff; + int tileExY = (mzz << 3) + zoff; + if (area != null && !area.containsPoint(baseExX + tileExX, baseExY + tileExY, basePlane)) + continue; + + int tileX = tileExX - ctx.sceneOffset; + int tileY = tileExY - ctx.sceneOffset; + Tile tile = tiles[0][tileExX][tileExY]; + + SceneTilePaint paint; + SceneTileModel model = null; + if (tile != null) { + paint = tile.getSceneTilePaint(); + model = tile.getSceneTileModel(); + + if (model == null) { + boolean hasTilePaint = paint != null && paint.getNeColor() != HIDDEN_HSL; + if (!hasTilePaint) { + tile = tile.getBridge(); + if (tile != null) { + paint = tile.getSceneTilePaint(); + model = tile.getSceneTileModel(); + hasTilePaint = paint != null && paint.getNeColor() != HIDDEN_HSL; + } + } + + if (hasTilePaint) + continue; + } else { + if (isInFillGapsRegion(ctx, tileX, tileY, sceneMin, sceneMax, regions)) { + uploadGapTileModelFaces(ctx, zone, tile, model, tileExX, tileExY, vb, fb); + } + continue; + } + } + + if (!isInFillGapsRegion(ctx, tileX, tileY, sceneMin, sceneMax, regions)) + continue; + + int sceneTileX = tileExX - ctx.sceneOffset; + int sceneTileY = tileExY - ctx.sceneOffset; + int lx = sceneTileX * LOCAL_TILE_SIZE - basex; + int lz = sceneTileY * LOCAL_TILE_SIZE - basez; + int lx0 = lx, lz0 = lz; + int lx1 = lx + LOCAL_TILE_SIZE, lz1 = lz; + int lx2 = lx + LOCAL_TILE_SIZE, lz2 = lz + LOCAL_TILE_SIZE; + int lx3 = lx, lz3 = lz + LOCAL_TILE_SIZE; + + int renderLevel = tile != null ? tile.getRenderLevel() : 0; + int swBot = tileHeights[renderLevel][tileExX][tileExY] + GAP_DEPTH_OFFSET; + int seBot = tileHeights[renderLevel][tileExX + 1][tileExY] + GAP_DEPTH_OFFSET; + int neBot = tileHeights[renderLevel][tileExX + 1][tileExY + 1] + GAP_DEPTH_OFFSET; + int nwBot = tileHeights[renderLevel][tileExX][tileExY + 1] + GAP_DEPTH_OFFSET; + int swTop = tileHeights[renderLevel][tileExX][tileExY]; + int seTop = tileHeights[renderLevel][tileExX + 1][tileExY]; + int neTop = tileHeights[renderLevel][tileExX + 1][tileExY + 1]; + int nwTop = tileHeights[renderLevel][tileExX][tileExY + 1]; + + int terrainData = HDUtils.packTerrainData(true, 0, WaterType.NONE, renderLevel); + int color = 0; + + // Only draw a side when the neighbor in that direction is NOT a gap - avoids duplicate faces + boolean drawSouth = !isFlatGapTile(ctx, tiles, tileExX, tileExY - 1, sceneMin, sceneMax, regions, area, baseExX, baseExY, basePlane); + boolean drawEast = !isFlatGapTile(ctx, tiles, tileExX + 1, tileExY, sceneMin, sceneMax, regions, area, baseExX, baseExY, basePlane); + boolean drawNorth = !isFlatGapTile(ctx, tiles, tileExX, tileExY + 1, sceneMin, sceneMax, regions, area, baseExX, baseExY, basePlane); + boolean drawWest = !isFlatGapTile(ctx, tiles, tileExX - 1, tileExY, sceneMin, sceneMax, regions, area, baseExX, baseExY, basePlane); + + int u0 = UP_NORMAL[0], u1 = UP_NORMAL[2], u2 = UP_NORMAL[1]; + int fi = fb.putFace(color, color, color, materialData, materialData, materialData, terrainData, terrainData, terrainData); + vb.putVertex(lx2, neBot, lz2, 0, 0, 0, u0, u1, u2, fi); + vb.putVertex(lx3, nwBot, lz3, 0, 0, 0, u0, u1, u2, fi); + vb.putVertex(lx1, seBot, lz1, 0, 0, 0, u0, u1, u2, fi); + fi = fb.putFace(color, color, color, materialData, materialData, materialData, terrainData, terrainData, terrainData); + vb.putVertex(lx0, swBot, lz0, 0, 0, 0, u0, u1, u2, fi); + vb.putVertex(lx1, seBot, lz1, 0, 0, 0, u0, u1, u2, fi); + vb.putVertex(lx3, nwBot, lz3, 0, 0, 0, u0, u1, u2, fi); + + if (drawSouth) putGapSide(vb, fb, lx0, lz0, swBot, swTop, lx1, lz1, seBot, seTop, NORTH_NORMAL, color, materialData, terrainData); + if (drawEast) putGapSide(vb, fb, lx1, lz1, seBot, seTop, lx2, lz2, neBot, neTop, WEST_NORMAL, color, materialData, terrainData); + if (drawNorth) putGapSide(vb, fb, lx2, lz2, neBot, neTop, lx3, lz3, nwBot, nwTop, SOUTH_NORMAL, color, materialData, terrainData); + if (drawWest) putGapSide(vb, fb, lx3, lz3, nwBot, nwTop, lx0, lz0, swBot, swTop, EAST_NORMAL, color, materialData, terrainData); + } + } + } + + private void uploadGapTileModelFaces( + ZoneSceneContext ctx, + Zone zone, + Tile tile, + SceneTileModel model, + int tileExX, int tileExY, + GpuIntBuffer vb, + GpuIntBuffer fb + ) { + final int[] triangleColorA = model.getTriangleColorA(); + final int faceCount = triangleColorA.length; + + Material blackMaterial = materialManager.getMaterial("BLACK"); + int materialData = blackMaterial.packMaterialData(ModelOverride.NONE, UvType.GEOMETRY, false); + int terrainData = HDUtils.packTerrainData(true, 0, WaterType.NONE, tile.getRenderLevel()); + + int sceneTileX = tileExX - ctx.sceneOffset; + int sceneTileY = tileExY - ctx.sceneOffset; + int tileBaseX = sceneTileX * LOCAL_TILE_SIZE - basex; + int tileBaseZ = sceneTileY * LOCAL_TILE_SIZE - basez; + + for (int face = 0; face < faceCount; ++face) { + if (triangleColorA[face] != HIDDEN_HSL) + continue; + + ProceduralGenerator.faceVertexKeys(tile, face, vertices, vertexKeys); + ProceduralGenerator.faceLocalVertices(tile, face, vertices); + + int lx0 = tileBaseX + vertices[0][0]; + int ly0 = vertices[0][2] + GAP_DEPTH_OFFSET; + int lz0 = tileBaseZ + vertices[0][1]; + + int lx1 = tileBaseX + vertices[1][0]; + int ly1 = vertices[1][2] + GAP_DEPTH_OFFSET; + int lz1 = tileBaseZ + vertices[1][1]; + + int lx2 = tileBaseX + vertices[2][0]; + int ly2 = vertices[2][2] + GAP_DEPTH_OFFSET; + int lz2 = tileBaseZ + vertices[2][1]; + + if (isHorizontalFace(lx0, ly0, lz0, lx1, ly1, lz1, lx2, ly2, lz2)) + continue; + + int vertexKeyA = vertexKeys[0]; + int vertexKeyB = vertexKeys[1]; + int vertexKeyC = vertexKeys[2]; + + int[] normalsA = ctx.vertexTerrainNormals.getOrDefault(vertexKeyA, UP_NORMAL); + int[] normalsB = ctx.vertexTerrainNormals.getOrDefault(vertexKeyB, UP_NORMAL); + int[] normalsC = ctx.vertexTerrainNormals.getOrDefault(vertexKeyC, UP_NORMAL); + + int color = 0; + int texturedFaceIdx = fb.putFace( + color, color, color, + materialData, materialData, materialData, + terrainData, terrainData, terrainData + ); + + vb.putVertex( + lx0, ly0, lz0, + 0, 0, 0, + normalsA[0], normalsA[2], normalsA[1], + texturedFaceIdx + ); + vb.putVertex( + lx2, ly2, lz2, + 0, 0, 0, + normalsC[0], normalsC[2], normalsC[1], + texturedFaceIdx + ); + vb.putVertex( + lx1, ly1, lz1, + 0, 0, 0, + normalsB[0], normalsB[2], normalsB[1], + texturedFaceIdx + ); + } + } + private void uploadZoneLevelRoof( ZoneSceneContext ctx, Zone zone,