diff --git a/schemas/model_overrides.schema.json b/schemas/model_overrides.schema.json index a9e2bc91fc..343b2118af 100644 --- a/schemas/model_overrides.schema.json +++ b/schemas/model_overrides.schema.json @@ -214,6 +214,10 @@ "type": "boolean", "description": "Will invert the strength blend applied to the model, where vertices near the top will be effected less." }, + "positionTileSnapping": { + "type": "integer", + "description": "Specify the amount of tiles that the model's position should be snapped to." + }, "depthBias": { "type": "integer", "description": "Specify our own depth bias, replacing the bias set by Jagex. Between 0-255." @@ -416,6 +420,10 @@ "type": "boolean", "description": "Will invert the strength blend applied to the model, where vertices near the top will be effected less." }, + "positionTileSnapping": { + "type": "integer", + "description": "Specify the amount of tiles that the model's position should be snapped to." + }, "depthBias": { "type": "integer", "description": "Specify our own depth bias, replacing the bias set by Jagex. Between 0-255." @@ -624,6 +632,10 @@ "type": "integer", "description": "Modifier value between -3 and 3 to control how strongly wind should displace the model." }, + "positionTileSnapping": { + "type": "integer", + "description": "Specify the amount of tiles that the model's position should be snapped to." + }, "invertDisplacementStrength": { "type": "boolean", "description": "Will invert the strength blend applied to the model, where vertices near the top will be effected less." diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index cb715fd25a..4c56e213e0 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -89,7 +89,6 @@ import rs117.hd.opengl.shader.ShaderIncludes; import rs117.hd.opengl.shader.TiledLightingShaderProgram; import rs117.hd.opengl.shader.UIShaderProgram; -import rs117.hd.opengl.uniforms.UBOCompute; import rs117.hd.opengl.uniforms.UBOGlobal; import rs117.hd.opengl.uniforms.UBOLights; import rs117.hd.opengl.uniforms.UBOUI; @@ -103,6 +102,7 @@ import rs117.hd.renderer.zone.SceneManager; import rs117.hd.renderer.zone.ZoneRenderer; import rs117.hd.scene.AreaManager; +import rs117.hd.scene.DisplacementManager; import rs117.hd.scene.EnvironmentManager; import rs117.hd.scene.FishingSpotReplacer; import rs117.hd.scene.GamevalManager; @@ -129,6 +129,7 @@ import rs117.hd.utils.ResourcePath; import rs117.hd.utils.ShaderRecompile; import rs117.hd.utils.buffer.GLBuffer; +import rs117.hd.utils.buffer.GLTextureBuffer; import rs117.hd.utils.collections.ConcurrentPool; import rs117.hd.utils.collections.PooledArrayType; import rs117.hd.utils.jobs.GenericJob; @@ -137,6 +138,8 @@ import static net.runelite.api.Constants.*; import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.HdPluginConfig.*; +import static rs117.hd.scene.DisplacementManager.MAX_BOAT_COUNT; +import static rs117.hd.scene.DisplacementManager.MAX_CHARACTER_POSITION_COUNT; import static rs117.hd.utils.MathUtils.*; import static rs117.hd.utils.ResourcePath.path; import static rs117.hd.utils.buffer.GLBuffer.DEBUG_MAC_OS; @@ -284,6 +287,9 @@ public class HdPlugin extends Plugin { @Inject private ModelOverrideManager modelOverrideManager; + @Inject + private DisplacementManager displacementManager; + @Inject private FishingSpotReplacer fishingSpotReplacer; @@ -722,6 +728,7 @@ protected void startUp() { fishingSpotReplacer.startUp(); gammaCalibrationOverlay.initialize(); npcDisplacementCache.initialize(); + displacementManager.initialize(); hasLoggedIn = client.getGameState().getState() > GameState.LOGGING_IN.getState(); redrawPreviousFrame = false; @@ -907,8 +914,10 @@ public ShaderIncludes getShaderIncludes() { var includes = new ShaderIncludes() .addIncludePath(SHADER_PATH) .addInclude("VERSION_HEADER", OSType.getOSType() == OSType.Linux ? LINUX_VERSION_HEADER : WINDOWS_VERSION_HEADER) + .define("TEXEL_SIZE", GLTextureBuffer.isRGBASupported() ? 4 : 3) .define("UI_SCALING_MODE", config.uiScalingMode()) .define("COLOR_BLINDNESS", config.colorBlindness()) + .define("DITHER_FADE", config.ditherFade()) .define("APPLY_COLOR_FILTER", configColorFilter != ColorFilter.NONE) .define("MATERIAL_COUNT", MaterialManager.MATERIALS.length) .define("WATER_TYPE_COUNT", waterTypeManager.uboWaterTypes.getCount()) @@ -931,7 +940,8 @@ public ShaderIncludes getShaderIncludes() { .define("WIND_DISPLACEMENT", configWindDisplacement) .define("WIND_DISPLACEMENT_NOISE_RESOLUTION", WIND_DISPLACEMENT_NOISE_RESOLUTION) .define("CHARACTER_DISPLACEMENT", configCharacterDisplacement) - .define("MAX_CHARACTER_POSITION_COUNT", max(1, UBOCompute.MAX_CHARACTER_POSITION_COUNT)) + .define("MAX_CHARACTER_POSITION_COUNT", max(1, MAX_CHARACTER_POSITION_COUNT)) + .define("MAX_BOAT_COUNT", max(1, MAX_BOAT_COUNT)) .define("WIREFRAME", config.wireframe()) .define("WINDOWS_HDR_CORRECTION", config.windowsHdrCorrection()) .define("LEGACY_RENDERER", renderer instanceof LegacyRenderer) @@ -1825,6 +1835,7 @@ public void processPendingConfigChanges() { case KEY_WIREFRAME: case KEY_SHADOW_FILTERING: case KEY_WINDOWS_HDR_CORRECTION: + case KEY_DITHER_FADE: recompilePrograms = true; break; case KEY_ANTI_ALIASING_MODE: diff --git a/src/main/java/rs117/hd/HdPluginConfig.java b/src/main/java/rs117/hd/HdPluginConfig.java index f79b21b215..c91b181003 100644 --- a/src/main/java/rs117/hd/HdPluginConfig.java +++ b/src/main/java/rs117/hd/HdPluginConfig.java @@ -1251,6 +1251,18 @@ default boolean multithreadedModelProcessing() { return true; } + String KEY_DITHER_FADE = "experimentalDitherFade"; + @ConfigItem( + keyName = KEY_DITHER_FADE, + name = "Dither fade", + description = "Let objects and chunks fade gradually, at the expense of GPU performance.", + section = experimentalSettings + ) + default boolean ditherFade() { + return false; + } + + /*====== Internal settings ======*/ @ConfigItem(keyName = "pluginUpdateMessage", hidden = true, name = "", description = "") diff --git a/src/main/java/rs117/hd/config/ObjectFadingMode.java b/src/main/java/rs117/hd/config/ObjectFadingMode.java new file mode 100644 index 0000000000..f12cd1e2e4 --- /dev/null +++ b/src/main/java/rs117/hd/config/ObjectFadingMode.java @@ -0,0 +1,7 @@ +package rs117.hd.config; + +public enum ObjectFadingMode { + NONE, + TRANSPARENCY, + DITHER +} diff --git a/src/main/java/rs117/hd/opengl/compute/OpenCLManager.java b/src/main/java/rs117/hd/opengl/compute/OpenCLManager.java index dee9a81aac..1d31a65ac3 100644 --- a/src/main/java/rs117/hd/opengl/compute/OpenCLManager.java +++ b/src/main/java/rs117/hd/opengl/compute/OpenCLManager.java @@ -48,7 +48,6 @@ import rs117.hd.HdPlugin; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; -import rs117.hd.opengl.uniforms.UBOCompute; import rs117.hd.renderer.legacy.LegacyRenderer; import rs117.hd.utils.buffer.SharedGLBuffer; @@ -61,6 +60,7 @@ import static org.lwjgl.system.MemoryUtil.NULL; import static org.lwjgl.system.MemoryUtil.memASCII; import static org.lwjgl.system.MemoryUtil.memUTF8; +import static rs117.hd.scene.DisplacementManager.MAX_CHARACTER_POSITION_COUNT; import static rs117.hd.utils.MathUtils.*; @Slf4j @@ -324,7 +324,7 @@ public void initializePrograms() throws ShaderException, IOException { .define("WIND_DISPLACEMENT", plugin.configWindDisplacement) .define("WIND_DISPLACEMENT_NOISE_RESOLUTION", HdPlugin.WIND_DISPLACEMENT_NOISE_RESOLUTION) .define("CHARACTER_DISPLACEMENT", plugin.configCharacterDisplacement) - .define("MAX_CHARACTER_POSITION_COUNT", UBOCompute.MAX_CHARACTER_POSITION_COUNT) + .define("MAX_CHARACTER_POSITION_COUNT", MAX_CHARACTER_POSITION_COUNT) .addIncludePath(OpenCLManager.class); passthroughProgram = compileProgram(stack, "comp_unordered.cl", includes); passthroughKernel = getKernel(stack, passthroughProgram, KERNEL_NAME_PASSTHROUGH); diff --git a/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java index 1c75140e78..cb2275a09a 100644 --- a/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java @@ -4,6 +4,7 @@ import static rs117.hd.HdPlugin.TEXTURE_UNIT_GAME; import static rs117.hd.HdPlugin.TEXTURE_UNIT_SHADOW_MAP; import static rs117.hd.HdPlugin.TEXTURE_UNIT_TILED_LIGHTING_MAP; +import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_MODEL_DATA; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; public class SceneShaderProgram extends ShaderProgram { @@ -11,6 +12,7 @@ public class SceneShaderProgram extends ShaderProgram { protected final UniformTexture uniShadowMap = addUniformTexture("shadowMap"); protected final UniformTexture uniTiledLightingTextureArray = addUniformTexture("tiledLightingArray"); protected final UniformTexture uniTextureFaces = addUniformTexture("textureFaces"); + protected final UniformTexture uniModelData = addUniformTexture("modelData"); public SceneShaderProgram() { super(t -> t @@ -25,12 +27,14 @@ protected void initialize() { uniShadowMap.set(TEXTURE_UNIT_SHADOW_MAP); uniTiledLightingTextureArray.set(TEXTURE_UNIT_TILED_LIGHTING_MAP); uniTextureFaces.set(TEXTURE_UNIT_TEXTURED_FACES); + uniModelData.set(TEXTURE_UNIT_MODEL_DATA); } public static class Legacy extends SceneShaderProgram { Legacy() { shaderTemplate.add(GL_GEOMETRY_SHADER, "scene_geom.glsl"); uniTextureFaces.ignoreMissing = true; + uniModelData.ignoreMissing = true; } } } diff --git a/src/main/java/rs117/hd/opengl/shader/ShadowShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/ShadowShaderProgram.java index 1663800a11..bdd8e42197 100644 --- a/src/main/java/rs117/hd/opengl/shader/ShadowShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/ShadowShaderProgram.java @@ -5,11 +5,13 @@ import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.HdPlugin.TEXTURE_UNIT_GAME; +import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_MODEL_DATA; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; public abstract class ShadowShaderProgram extends ShaderProgram { protected final UniformTexture uniTextureArray = addUniformTexture("textureArray"); protected final UniformTexture uniTextureFaces = addUniformTexture("textureFaces"); + protected final UniformTexture uniModelData = addUniformTexture("modelData"); protected ShadowMode mode; @@ -23,6 +25,7 @@ public abstract class ShadowShaderProgram extends ShaderProgram { protected void initialize() { uniTextureArray.set(TEXTURE_UNIT_GAME); uniTextureFaces.set(TEXTURE_UNIT_TEXTURED_FACES); + uniModelData.set(TEXTURE_UNIT_MODEL_DATA); } @Override @@ -47,6 +50,8 @@ public static class Legacy extends ShadowShaderProgram { public Legacy setMode(ShadowMode mode) { this.mode = mode; uniTextureArray.ignoreMissing = mode != ShadowMode.DETAILED; + uniTextureFaces.ignoreMissing = true; + uniModelData.ignoreMissing = true; return this; } diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOCompute.java b/src/main/java/rs117/hd/opengl/uniforms/UBOCompute.java index 5dee80aa6c..156c4c05e5 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UBOCompute.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UBOCompute.java @@ -1,20 +1,14 @@ package rs117.hd.opengl.uniforms; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; +import javax.inject.Inject; +import rs117.hd.scene.DisplacementManager; import rs117.hd.utils.buffer.SharedGLBuffer; import static org.lwjgl.opencl.CL10.*; import static org.lwjgl.opengl.GL33C.*; -import static rs117.hd.utils.MathUtils.*; +import static rs117.hd.scene.DisplacementManager.MAX_CHARACTER_POSITION_COUNT; public class UBOCompute extends UniformBuffer { - public static final int MAX_CHARACTER_POSITION_COUNT = 50; - - private static final Comparator CHARACTER_POSITION_PAIR_COMPARATOR = - Comparator.comparingDouble(p -> p.dist); - // Camera uniforms public Property yaw = addProperty(PropertyType.Float, "yaw"); public Property pitch = addProperty(PropertyType.Float, "pitch"); @@ -33,74 +27,17 @@ public class UBOCompute extends UniformBuffer { public Property windOffset = addProperty(PropertyType.Float, "windOffset"); private final Property characterPositionCount = addProperty(PropertyType.Int, "characterPositionCount"); - private final Property[] characterPositions = addPropertyArray(PropertyType.FVec3, "characterPositions", MAX_CHARACTER_POSITION_COUNT); + private final Property[] characterPositions = addPropertyArray(PropertyType.FVec4, "characterPositions", MAX_CHARACTER_POSITION_COUNT); - private final ArrayList characterPositionsPairs = new ArrayList<>(characterPositions.length); - private int writtenCharacterPositions; - private float playerPosX, playerPosZ; - - private static class CharacterPositionPair { - public float x; - public float z; - public float radius; - public float dist = Float.MAX_VALUE; - } + @Inject + private DisplacementManager displacementManager; public UBOCompute() { super(GL_DYNAMIC_DRAW, CL_MEM_READ_ONLY); } - private CharacterPositionPair getCharacterPositionPair() { - if (writtenCharacterPositions >= characterPositionsPairs.size()) { - CharacterPositionPair newPair = new CharacterPositionPair(); - characterPositionsPairs.add(newPair); - return newPair; - } - - return characterPositionsPairs.get(writtenCharacterPositions); - } - - public void addCharacterPosition(int localX, int localZ, int modelRadius) { - int writeIndex = writtenCharacterPositions; - CharacterPositionPair pair = getCharacterPositionPair(); - characterPositionsPairs.remove(writeIndex); - - pair.x = localX; - pair.z = localZ; - pair.radius = modelRadius * 1.25f; - - if (writeIndex == 0) { - playerPosX = pair.x; - playerPosZ = pair.z; - pair.dist = 0.0f; - } else { - pair.dist = abs(playerPosX - pair.x) + abs(playerPosZ - pair.z); - - if (writeIndex > 1) { - int index = Collections.binarySearch( - characterPositionsPairs.subList(1, writeIndex), - pair, - CHARACTER_POSITION_PAIR_COMPARATOR - ); - - writeIndex = index >= 0 ? index : -index - 1; - } - } - - characterPositionsPairs.add(writeIndex, pair); - writtenCharacterPositions++; - } - @Override protected void preUpload() { - for (int i = 0; i < writtenCharacterPositions; i++) { - CharacterPositionPair pair = characterPositionsPairs.get(i); - pair.dist = Float.MAX_VALUE; - - if (i < characterPositions.length) - characterPositions[i].set(pair.x, pair.z, pair.radius); - } - characterPositionCount.set(min(writtenCharacterPositions, characterPositions.length)); - writtenCharacterPositions = 0; + displacementManager.writeCharacterPositions(characterPositions, characterPositionCount); } } diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBODisplacement.java b/src/main/java/rs117/hd/opengl/uniforms/UBODisplacement.java new file mode 100644 index 0000000000..af08a50d8b --- /dev/null +++ b/src/main/java/rs117/hd/opengl/uniforms/UBODisplacement.java @@ -0,0 +1,41 @@ +package rs117.hd.opengl.uniforms; + +import javax.inject.Inject; +import rs117.hd.scene.DisplacementManager; +import rs117.hd.utils.buffer.GLBuffer; + +import static org.lwjgl.opengl.GL33C.*; +import static rs117.hd.scene.DisplacementManager.MAX_BOAT_COUNT; +import static rs117.hd.scene.DisplacementManager.MAX_CHARACTER_POSITION_COUNT; + +public class UBODisplacement extends UniformBuffer { + public Property windDirectionX = addProperty(PropertyType.Float, "windDirectionX"); + public Property windDirectionZ = addProperty(PropertyType.Float, "windDirectionZ"); + public Property windStrength = addProperty(PropertyType.Float, "windStrength"); + public Property windCeiling = addProperty(PropertyType.Float, "windCeiling"); + public Property windOffset = addProperty(PropertyType.Float, "windOffset"); + + private final Property characterPositionCount = addProperty(PropertyType.Int, "characterPositionCount"); + private final Property boatAABBCount = addProperty(PropertyType.Int, "boatCount"); + + private final Property[] characterPositions = addPropertyArray(PropertyType.FVec4, "characterPositions", MAX_CHARACTER_POSITION_COUNT); + private final BoatStruct[] boatData = addStructs(new BoatStruct[MAX_BOAT_COUNT], BoatStruct::new); + + @Inject + private DisplacementManager displacementManager; + + public UBODisplacement() { + super(GL_DYNAMIC_DRAW); + } + + @Override + protected void preUpload() { + displacementManager.writeCharacterPositions(characterPositions, characterPositionCount); + displacementManager.writeBoatData(boatData, boatAABBCount); + } + + public class BoatStruct extends StructProperty { + // Encoded Half16 + public Property[] contour = addPropertyArray(PropertyType.IVec4, "contour", 2); + } +} diff --git a/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java b/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java index 0d1ca3104f..21d2fc6724 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java @@ -221,6 +221,34 @@ public final void set(float x, float y, float z, float w) { owner.dataFloat.position(offset).put(x).put(y).put(z).put(w); owner.markWaterLine(position, type.size); } + + public final void set(int[] data, int offset, int length) { + if (isUninitialized()) + return; + + if (type.elementCount != length) { + log("Int setter was provided with incorrect number of elements: " + length + " != " + type.elementCount); + return; + } + + owner.dataInt.position(this.offset); + owner.dataInt.put(data, offset, length); + owner.markWaterLine(position, type.size); + } + + public final void set(float[] data, int offset, int length) { + if (isUninitialized()) + return; + + if (type.elementCount != length) { + log("Float setter was provided with incorrect number of elements: " + length + " != " + type.elementCount); + return; + } + + owner.dataFloat.position(this.offset); + owner.dataFloat.put(data, offset, length); + owner.markWaterLine(position, type.size); + } } public interface CreateStructProperty { diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index 5f998a8651..bb43674754 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -66,7 +66,7 @@ public enum Timer { UPLOAD_GEOMETRY(GPU_TIMER), UPLOAD_UI(GPU_TIMER, "Upload UI"), COMPUTE(GPU_TIMER), - UNMAP_ROOT_CTX(GPU_TIMER), + FRAME_CONTEXT_UNMAP(GPU_TIMER), CLEAR_SCENE(GPU_TIMER), RENDER_SHADOWS(GPU_TIMER), RENDER_SCENE(GPU_TIMER), diff --git a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java index 41ce789d62..be3bca312f 100644 --- a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java +++ b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java @@ -42,6 +42,7 @@ import rs117.hd.overlays.Timer; import rs117.hd.renderer.Renderer; import rs117.hd.scene.AreaManager; +import rs117.hd.scene.DisplacementManager; import rs117.hd.scene.EnvironmentManager; import rs117.hd.scene.FishingSpotReplacer; import rs117.hd.scene.LightManager; @@ -109,6 +110,9 @@ public class LegacyRenderer implements Renderer { @Inject private ModelOverrideManager modelOverrideManager; + @Inject + private DisplacementManager displacementManager; + @Inject private LegacySceneUploader sceneUploader; @@ -139,6 +143,9 @@ public class LegacyRenderer implements Renderer { @Inject private JobSystem jobSystem; + @Inject + private UBOCompute uboCompute; + private final ComputeMode computeMode = HdPlugin.APPLE ? ComputeMode.OPENCL : ComputeMode.OPENGL; private final List modelSortingComputePrograms = new ArrayList<>(); @@ -177,8 +184,6 @@ public class LegacyRenderer implements Renderer { private LegacySceneContext nextSceneContext; private int gameTicksUntilSceneReload; - private UBOCompute uboCompute; - @Override public boolean supportsGpu(GLCapabilities glCaps) { return computeMode == ComputeMode.OPENGL ? glCaps.OpenGL43 : glCaps.OpenGL31; @@ -432,7 +437,6 @@ private void initializeBuffers() { hRenderBufferNormals = new SharedGLBuffer("Render Normals", GL_ARRAY_BUFFER, GL_STREAM_COPY, CL_MEM_WRITE_ONLY); hModelPassthroughBuffer = new SharedGLBuffer("Model Passthrough", GL_ARRAY_BUFFER, GL_STREAM_DRAW, CL_MEM_READ_ONLY); - uboCompute = new UBOCompute(); uboCompute.initialize(UNIFORM_BLOCK_COMPUTE); modelPassthroughBuffer = new GpuIntBuffer(); @@ -450,7 +454,6 @@ private void initializeBuffers() { private void destroyBuffers() { uboCompute.destroy(); - uboCompute = null; hStagingBufferVertices.destroy(); hStagingBufferUvs.destroy(); @@ -645,13 +648,7 @@ public void drawScene(double cameraX, double cameraY, double cameraZ, double cam uboCompute.windCeiling.set(environmentManager.currentWindCeiling); uboCompute.windOffset.set(plugin.windOffset); - if (plugin.configCharacterDisplacement && localPlayer != null) { - // The local player needs to be added first for distance culling - var lp = localPlayer.getLocalLocation(); - Model playerModel = localPlayer.getModel(); - if (playerModel != null) - uboCompute.addCharacterPosition(lp.getX(), lp.getY(), (int) (Perspective.LOCAL_TILE_SIZE * 1.33f)); - } + displacementManager.addLocalPlayer(); // Calculate the viewport dimensions before scaling in order to include the extra padding int viewportWidth = (int) (plugin.sceneViewport[2] / plugin.sceneViewportScale[0]); @@ -1744,29 +1741,7 @@ public void draw(Projection projection, @Nullable Scene scene, Renderable render if (eightIntWrite[0] != -1) plugin.drawnDynamicRenderableCount = plugin.drawnDynamicRenderableCount + 1; - if (plugin.configCharacterDisplacement && renderable instanceof Actor) { - if (plugin.enableDetailedTimers) - frameTimer.begin(Timer.CHARACTER_DISPLACEMENT); - if (renderable instanceof NPC) { - var npc = (NPC) renderable; - var entry = npcDisplacementCache.get(npc); - if (entry.canDisplace) { - int displacementRadius = entry.idleRadius; - if (displacementRadius == -1) { - displacementRadius = modelRadius; // Fallback to model radius since we don't know the idle radius yet - if (npc.getIdlePoseAnimation() == npc.getPoseAnimation() && npc.getAnimation() == -1) { - displacementRadius *= 2; // Double the idle radius, so that it fits most other animations - entry.idleRadius = displacementRadius; - } - } - uboCompute.addCharacterPosition(x, z, displacementRadius); - } - } else if (renderable instanceof Player && renderable != client.getLocalPlayer()) { - uboCompute.addCharacterPosition(x, z, (int) (Perspective.LOCAL_TILE_SIZE * 1.33f)); - } - if (plugin.enableDetailedTimers) - frameTimer.end(Timer.CHARACTER_DISPLACEMENT); - } + displacementManager.addCharacterPosition(sceneContext.scene, x, z, renderable, model); } if (plugin.enableDetailedTimers) diff --git a/src/main/java/rs117/hd/renderer/zone/AsyncCachedModel.java b/src/main/java/rs117/hd/renderer/zone/AsyncCachedModel.java index 48ea8b00af..d814f9b723 100644 --- a/src/main/java/rs117/hd/renderer/zone/AsyncCachedModel.java +++ b/src/main/java/rs117/hd/renderer/zone/AsyncCachedModel.java @@ -102,7 +102,6 @@ public static void destroy() { private final CachedArrayField vertexNormalsY = addField(INT, VERTEX_TYPE); private final CachedArrayField vertexNormalsZ = addField(INT, VERTEX_TYPE); - private final AtomicBoolean isProcessing = new AtomicBoolean(false); private final AtomicBoolean isCompleted = new AtomicBoolean(false); private WorldViewContext ctx; private Projection projection; @@ -114,6 +113,7 @@ public static void destroy() { private AlphaModel alphaModel; private boolean isModelPartiallyVisible; private int drawIndex; + private float fade; private int orientation; private int x; private int y; @@ -218,6 +218,7 @@ public synchronized void queue( AlphaModel alphaModel, boolean isModelPartiallyVisible, int drawIndex, + float fade, int orientation, int x, int y, int z, @Nonnull UploadModelFunc uploadFunc @@ -231,6 +232,7 @@ public synchronized void queue( this.alphaModel = alphaModel; this.isModelPartiallyVisible = isModelPartiallyVisible; this.drawIndex = drawIndex; + this.fade = fade; this.orientation = orientation; this.x = x; this.y = y; @@ -267,7 +269,6 @@ public synchronized void queue( if (alphaModel != null) zone.pendingModelJobs.add(this); - isProcessing.set(false); isCompleted.set(false); INFLIGHT.add(this); @@ -314,9 +315,6 @@ private boolean processCachedFields(Model model, boolean cache) { @Override protected boolean canStart() { - if (isProcessing.get()) // Work has been stolen, so pop it off the queue - return true; - return verticesX.isCached() && verticesY.isCached() && verticesZ.isCached() && faceIndices1.isCached() && faceIndices2.isCached() && faceIndices3.isCached() && @@ -325,13 +323,6 @@ protected boolean canStart() { @Override protected void onRun() { - processModel(); - } - - public boolean processModel() { - if (!isProcessing.compareAndSet(false, true)) - return false; - try { uploadFunc.upload( ctx, @@ -344,6 +335,7 @@ public boolean processModel() { alphaModel, isModelPartiallyVisible, drawIndex, + fade, orientation, x, y, z ); @@ -373,8 +365,6 @@ public boolean processModel() { INFLIGHT.remove(this); POOL.recycle(this); } - - return true; } @Override @@ -457,6 +447,7 @@ void upload( AlphaModel alphaModel, boolean isModelPartiallyVisible, int drawIndex, + float fade, int orientation, int x, int y, int z ); diff --git a/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java b/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java index 5e1dccb3c0..cdf55dabae 100644 --- a/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java +++ b/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java @@ -1,10 +1,11 @@ package rs117.hd.renderer.zone; +import java.nio.IntBuffer; import java.util.ArrayDeque; import java.util.Arrays; -import javax.annotation.Nonnull; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.lwjgl.system.MemoryStack; import rs117.hd.utils.CommandBuffer; import rs117.hd.utils.Destructible; import rs117.hd.utils.buffer.GLBuffer; @@ -14,9 +15,13 @@ import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.HdPlugin.GL_CAPS; -import static rs117.hd.HdPlugin.NVIDIA_GPU; import static rs117.hd.HdPlugin.SUPPORTS_INDIRECT_DRAW; import static rs117.hd.HdPlugin.SUPPORTS_STORAGE_BUFFERS; +import static rs117.hd.renderer.zone.Zone.METADATA_SIZE; +import static rs117.hd.renderer.zone.Zone.MODEL_DATA_INTS; +import static rs117.hd.renderer.zone.Zone.VERT_SIZE; +import static rs117.hd.renderer.zone.Zone.VERT_SIZE_INTS; +import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_MODEL_DATA; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; import static rs117.hd.utils.MathUtils.*; import static rs117.hd.utils.buffer.GLBuffer.STORAGE_IMMUTABLE; @@ -27,94 +32,97 @@ public class DynamicModelVAO implements Destructible { public static final int INITIAL_SIZE = (int) (8 * MiB); - // Temp vertex format - // pos float vec3(x, y, z) - // uvw short vec3(u, v, w) - // normal short vec3(nx, ny, nz) - static final int VERT_SIZE = 32; - static final int VERT_SIZE_INTS = VERT_SIZE / 4; - - // Metadata format - // worldViewIndex int - // dummy sceneOffset ivec2 for macOS workaround - static final int METADATA_SIZE = 12; - @Getter private int vao; private final GLBuffer vboRender; private final GLBuffer vboStaging; - private final GLTextureBuffer tbo; + private final GLBuffer stubMetadata; + private final GLTextureBuffer tboF; + private final GLTextureBuffer tboM; private final ArrayDeque usedViews = new ArrayDeque<>(); private final ArrayDeque freeViews = new ArrayDeque<>(); private final GLMappedBufferIntWriter vboWriter; - private final GLMappedBufferIntWriter tboWriter; + private final GLMappedBufferIntWriter tboFWriter; + private final GLMappedBufferIntWriter tboMWriter; private boolean isMapped = false; + private int[] drawOffsets = new int[16]; private int[] drawCounts = new int[16]; - private int writtenRangeCount; - private int drawRangeCount; - private long[] srcCopyOffsets = new long[16]; - private long[] dstCopyOffsets = new long[16]; - private long[] copyNumBytes = new long[16]; + private int[] packedOffsets = new int[16]; + private int[] mergedOffsets = new int[16]; + private int[] mergedCounts = new int[16]; + + private long[] pendingCopySrcOffsets = new long[16]; + private long[] pendingCopyDstOffsets = new long[16]; + private long[] pendingCopyNumBytes = new long[16]; + private int pendingCopyCount; + private int nextPackedOffset; + + private int allocatedDrawCount; + private int writtenRangeCount; DynamicModelVAO(String name, boolean useStagingBuffer) { if (useStagingBuffer && SUPPORTS_STORAGE_BUFFERS) { - this.vboRender = new GLBuffer("VAO::VBO::" + name, GL_ARRAY_BUFFER, GL_STATIC_DRAW, 0); + this.vboRender = new GLBuffer("DynamicModel::VBO::" + name, GL_ARRAY_BUFFER, GL_STATIC_DRAW, 0); this.vboStaging = new GLBuffer( - "VAO::VBO_STAGING::" + name, + "DynamicModel::VBO_STAGING::" + name, GL_ARRAY_BUFFER, GL_STREAM_DRAW, STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE ); } else { this.vboRender = this.vboStaging = new GLBuffer( - "VAO::VBO::" + name, + "DynamicModel::VBO::" + name, GL_ARRAY_BUFFER, GL_STREAM_DRAW, STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE ); } + this.stubMetadata = new GLBuffer("DynamicModel::Metadata", GL_ARRAY_BUFFER, GL_STATIC_DRAW); this.vboWriter = new GLMappedBufferIntWriter(this.vboStaging); - this.tbo = new GLTextureBuffer("VAO::TBO::" + name, GL_STREAM_DRAW, STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE); - this.tboWriter = new GLMappedBufferIntWriter(this.tbo); + this.tboF = new GLTextureBuffer( + "DynamicModel::TexturedFaces::" + name, + GL_STREAM_DRAW, + STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE + ); + this.tboM = new GLTextureBuffer( + "DynamicModel::ModelData::" + name, + GL_STREAM_DRAW, + STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE + ); + this.tboFWriter = new GLMappedBufferIntWriter(this.tboF); + this.tboMWriter = new GLMappedBufferIntWriter(this.tboM); + + Arrays.fill(packedOffsets, -1); } public boolean hasStagingBuffer() { return vboRender != vboStaging; } void initialize() { vao = glGenVertexArrays(); - tbo.initialize(INITIAL_SIZE); + tboF.initialize(INITIAL_SIZE); + tboM.initialize(INITIAL_SIZE); vboRender.initialize(INITIAL_SIZE); if (vboRender != vboStaging) vboStaging.initialize(INITIAL_SIZE); - bindRenderVAO(); - } - - public void bindMetadataVAO(@Nonnull GLBuffer vboMetadata) { - glBindVertexArray(vao); - glBindBuffer(GL_ARRAY_BUFFER, vboMetadata.id); - - // WorldView index (not ID) - glEnableVertexAttribArray(6); - glVertexAttribDivisor(6, 1); - glVertexAttribIPointer(6, 1, GL_INT, METADATA_SIZE, 0); - - if (!NVIDIA_GPU) { - // Workaround for incorrect implementations of disabled vertex attribs, particularly on macOS - glEnableVertexAttribArray(7); - glVertexAttribDivisor(7, 1); - glVertexAttribIPointer(7, 2, GL_INT, METADATA_SIZE, 4); + // Build Stub Metadata + stubMetadata.initialize(METADATA_SIZE); + try (MemoryStack stack = MemoryStack.stackPush()) { + int intsCount = METADATA_SIZE / Integer.BYTES; + IntBuffer buf = stack.mallocInt(intsCount); + for (int i = 0; i < intsCount; i++) + buf.put(0); + stubMetadata.upload(buf); } - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); + bindRenderVAO(); } void bindRenderVAO() { @@ -123,66 +131,63 @@ void bindRenderVAO() { // Position glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, false, VERT_SIZE, 0); + glVertexAttribPointer(0, 4, GL_HALF_FLOAT, false, VERT_SIZE, 0); // UVs glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 4, GL_HALF_FLOAT, false, VERT_SIZE, 12); + glVertexAttribPointer(1, 4, GL_HALF_FLOAT, false, VERT_SIZE, 8); // Normals glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 4, GL_SHORT, false, VERT_SIZE, 20); + glVertexAttribPointer(2, 4, GL_SHORT, false, VERT_SIZE, 16); // TextureFaceIdx glEnableVertexAttribArray(3); - glVertexAttribIPointer(3, 1, GL_INT, VERT_SIZE, 28); + glVertexAttribIPointer(3, 1, GL_INT, VERT_SIZE, 24); + + glBindBuffer(GL_ARRAY_BUFFER, stubMetadata.id); + + // WorldView index (not ID) + glEnableVertexAttribArray(6); + glVertexAttribDivisor(6, 1); + glVertexAttribIPointer(6, 1, GL_INT, METADATA_SIZE, 0); + + // Scene offset + glEnableVertexAttribArray(7); + glVertexAttribDivisor(7, 1); + glVertexAttribIPointer(7, 2, GL_INT, METADATA_SIZE, 4); + + // Scene offset + glEnableVertexAttribArray(8); + glVertexAttribDivisor(8, 1); + glVertexAttribPointer(8, 1, GL_FLOAT, false, METADATA_SIZE, 12); + glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } void map() { + if (!vboRender.isStorageBuffer()) + vboRender.orphan(); + vboWriter.map(false); - tboWriter.map(false); + tboFWriter.map(false); + tboMWriter.map(false); reset(); isMapped = true; } - synchronized void unmap(boolean coalesce) { + synchronized void unmap() { final int renderVBOId = vboRender.id; - long vboWrittenBytes = vboWriter.flush(); - tboWriter.flush(); - - if (drawRangeCount > 0) { - mergeRanges(); - - if (hasStagingBuffer()) { - vboRender.orphan(); - if (drawRangeCount > 1 && coalesce) { - if (srcCopyOffsets.length < drawRangeCount) { - srcCopyOffsets = new long[drawRangeCount]; - dstCopyOffsets = new long[drawRangeCount]; - copyNumBytes = new long[drawRangeCount]; - } - - long dstOffset = 0; - for (int i = 0; i < drawRangeCount; i++) { - srcCopyOffsets[i] = drawOffsets[i] * (long) VERT_SIZE; - dstCopyOffsets[i] = dstOffset; - copyNumBytes[i] = drawCounts[i] * (long) VERT_SIZE; - dstOffset += copyNumBytes[i]; - } - vboStaging.copyMultiTo(vboRender, srcCopyOffsets, dstCopyOffsets, copyNumBytes, drawRangeCount); - - drawOffsets[0] = 0; - drawCounts[0] = (int) (dstOffset / VERT_SIZE); - drawRangeCount = 1; - } else { - vboStaging.copyTo(vboRender, 0, 0, vboWrittenBytes); - } - } - } + vboWriter.flush(); + tboFWriter.flush(); + tboMWriter.flush(); + + if (pendingCopyCount > 0) + vboStaging.copyMultiTo(vboRender, pendingCopySrcOffsets, pendingCopyDstOffsets, pendingCopyNumBytes, pendingCopyCount); + pendingCopyCount = 0; if (renderVBOId != vboRender.id) bindRenderVAO(); @@ -192,10 +197,12 @@ synchronized void unmap(boolean coalesce) { @Override public void destroy() { vboWriter.destroy(); - tboWriter.destroy(); + tboFWriter.destroy(); + tboMWriter.destroy(); vboRender.destroy(); vboStaging.destroy(); - tbo.destroy(); + tboF.destroy(); + tboM.destroy(); if (vao != 0) glDeleteVertexArrays(vao); @@ -203,11 +210,16 @@ public void destroy() { } synchronized int obtainDrawIndex() { - int drawIndex = drawRangeCount++; - if (drawRangeCount >= drawOffsets.length) { - int oldLength = drawOffsets.length; + final int drawIndex = allocatedDrawCount++; + if (allocatedDrawCount >= drawOffsets.length) { + final int oldLength = drawOffsets.length; + final int oldPackedLength = packedOffsets.length; + drawOffsets = Arrays.copyOf(drawOffsets, oldLength * 2); drawCounts = Arrays.copyOf(drawCounts, oldLength * 2); + packedOffsets = Arrays.copyOf(packedOffsets, oldLength * 2); + + Arrays.fill(packedOffsets, oldPackedLength, packedOffsets.length, -1); } return drawIndex; } @@ -220,7 +232,7 @@ public synchronized View beginDraw(int drawIdx, int faceCount) { if (drawIdx == -1) drawIdx = obtainDrawIndex(); - assert drawIdx >= 0 && drawIdx < drawRangeCount : "drawIdx " + drawIdx + " out of bounds from 0 to " + drawRangeCount; + assert drawIdx >= 0 && drawIdx < allocatedDrawCount : "drawIdx " + drawIdx + " out of bounds from 0 to " + allocatedDrawCount; assert drawOffsets[drawIdx] == 0 && drawCounts[drawIdx] == 0 : String.format( "Provided draw index is already in use: %d %d %d", drawIdx, drawOffsets[drawIdx], drawCounts[drawIdx] @@ -231,9 +243,11 @@ public synchronized View beginDraw(int drawIdx, int faceCount) { if (view == null) view = new View(); view.vbo = vboWriter.reserve(faceCount * 3 * VERT_SIZE_INTS); - view.tbo = tboWriter.reserve(faceCount * 9); + view.tboF = tboFWriter.reserve(faceCount * 4); + view.tboM = tboMWriter.reserve(MODEL_DATA_INTS); view.vao = vao; - view.tboTexId = tbo.getTexId(); + view.tboFId = tboF.getTexId(); + view.tboMId = tboM.getTexId(); view.drawIdx = drawIdx; return view; @@ -252,47 +266,102 @@ private synchronized void endDraw(View view) { // Clear ReservedViews before returning to pool view.vbo = null; - view.tbo = null; + view.tboF = null; + view.tboM = null; usedViews.add(view); } - void mergeRanges() { - drawRangeCount = 0; - for (int i = 0; i < writtenRangeCount; i++) { - if (drawCounts[i] <= 0) + private void addPendingCopy(int srcOffsetInts, int dstOffsetInts, int countInts) { + long srcBytes = srcOffsetInts * (long) VERT_SIZE; + long dstBytes = dstOffsetInts * (long) VERT_SIZE; + long numBytes = countInts * (long) VERT_SIZE; + + if (pendingCopyCount > 0) { + int last = pendingCopyCount - 1; + if ( + pendingCopySrcOffsets[last] + pendingCopyNumBytes[last] == srcBytes && + pendingCopyDstOffsets[last] + pendingCopyNumBytes[last] == dstBytes + ) { + pendingCopyNumBytes[last] += numBytes; + return; + } + } + + if (pendingCopyCount >= pendingCopySrcOffsets.length) { + pendingCopySrcOffsets = Arrays.copyOf(pendingCopySrcOffsets, pendingCopySrcOffsets.length * 2); + pendingCopyDstOffsets = Arrays.copyOf(pendingCopyDstOffsets, pendingCopyDstOffsets.length * 2); + pendingCopyNumBytes = Arrays.copyOf(pendingCopyNumBytes, pendingCopyNumBytes.length * 2); + } + + pendingCopySrcOffsets[pendingCopyCount] = srcBytes; + pendingCopyDstOffsets[pendingCopyCount] = dstBytes; + pendingCopyNumBytes[pendingCopyCount] = numBytes; + pendingCopyCount++; + } + + private int mergeRanges(int fromDrawIdx, int toDrawIdx) { + int count = 0; + int maxCount = toDrawIdx - fromDrawIdx; + if (mergedOffsets.length < maxCount) { + mergedOffsets = Arrays.copyOf(mergedOffsets, maxCount); + mergedCounts = Arrays.copyOf(mergedCounts, maxCount); + } + + final boolean staging = hasStagingBuffer(); + final int[] mOffsets = mergedOffsets; + final int[] mCounts = mergedCounts; + + for (int i = fromDrawIdx; i < toDrawIdx; i++) { + int c = drawCounts[i]; + if (c <= 0) continue; - if (drawRangeCount > 0 && drawOffsets[drawRangeCount - 1] + drawCounts[drawRangeCount - 1] == drawOffsets[i]) { - drawCounts[drawRangeCount - 1] += drawCounts[i]; - } else { - if (drawRangeCount != i) { - drawOffsets[drawRangeCount] = drawOffsets[i]; - drawCounts[drawRangeCount] = drawCounts[i]; + int offset; + if (staging) { + int packed = packedOffsets[i]; + if (packed == -1) { + packed = nextPackedOffset; + nextPackedOffset += c; + addPendingCopy(drawOffsets[i], packed, c); + packedOffsets[i] = packed; } - drawRangeCount++; + offset = packed; + } else { + offset = drawOffsets[i]; + } + + if (count > 0 && mOffsets[count - 1] + mCounts[count - 1] == offset) { + mCounts[count - 1] += c; + } else { + mOffsets[count] = offset; + mCounts[count] = c; + count++; } } + return count; } - void draw(CommandBuffer cmd) { - if (drawRangeCount <= 0) + void draw(CommandBuffer cmd, int fromDrawIdx, int toDrawIdx) { + int rangeCount = mergeRanges(fromDrawIdx, toDrawIdx); + if (rangeCount <= 0) return; cmd.BindVertexArray(vao); - cmd.BindTextureUnit(GL_TEXTURE_BUFFER, tbo.getTexId(), TEXTURE_UNIT_TEXTURED_FACES); + cmd.BindTextureUnit(GL_TEXTURE_BUFFER, tboF.getTexId(), TEXTURE_UNIT_TEXTURED_FACES); + cmd.BindTextureUnit(GL_TEXTURE_BUFFER, tboM.getTexId(), TEXTURE_UNIT_MODEL_DATA); - if (drawRangeCount == 1) { + if (rangeCount == 1) { if (GL_CAPS.OpenGL40 && SUPPORTS_INDIRECT_DRAW) { - cmd.DrawArraysIndirect(GL_TRIANGLES, drawOffsets[0], drawCounts[0], ZoneRenderer.indirectDrawCmdsStaging); + cmd.DrawArraysIndirect(GL_TRIANGLES, mergedOffsets[0], mergedCounts[0], ZoneRenderer.indirectDrawCmdsStaging); } else { - cmd.DrawArrays(GL_TRIANGLES, drawOffsets[0], drawCounts[0]); + cmd.DrawArrays(GL_TRIANGLES, mergedOffsets[0], mergedCounts[0]); } } else { if (GL_CAPS.OpenGL43 && SUPPORTS_INDIRECT_DRAW) { - cmd.MultiDrawArraysIndirect(GL_TRIANGLES, drawOffsets, drawCounts, drawRangeCount, ZoneRenderer.indirectDrawCmdsStaging); + cmd.MultiDrawArraysIndirect(GL_TRIANGLES, mergedOffsets, mergedCounts, rangeCount, ZoneRenderer.indirectDrawCmdsStaging); } else { - cmd.MultiDrawArrays(GL_TRIANGLES, drawOffsets, drawCounts, drawRangeCount); + cmd.MultiDrawArrays(GL_TRIANGLES, mergedOffsets, mergedCounts, rangeCount); } } } @@ -300,16 +369,24 @@ void draw(CommandBuffer cmd) { void reset() { Arrays.fill(drawOffsets, 0, writtenRangeCount, 0); Arrays.fill(drawCounts, 0, writtenRangeCount, 0); - drawRangeCount = 0; + Arrays.fill(packedOffsets, 0, writtenRangeCount, -1); + allocatedDrawCount = 0; + writtenRangeCount = 0; + nextPackedOffset = 0; + pendingCopyCount = 0; freeViews.addAll(usedViews); usedViews.clear(); } public final class View { public ReservedView vbo; - public ReservedView tbo; + public ReservedView tboF; + public ReservedView tboM; public int vao; - public int tboTexId; + public int tboFId; + public int tboMId; + + @Getter private int drawIdx; public int getStartOffset() { diff --git a/src/main/java/rs117/hd/renderer/zone/FrameContext.java b/src/main/java/rs117/hd/renderer/zone/FrameContext.java new file mode 100644 index 0000000000..932488fd5c --- /dev/null +++ b/src/main/java/rs117/hd/renderer/zone/FrameContext.java @@ -0,0 +1,42 @@ +package rs117.hd.renderer.zone; + +public class FrameContext { + public static final int VAO_OPAQUE = 0; + public static final int VAO_ALPHA = 1; + public static final int VAO_PLAYER = 2; + public static final int VAO_SHADOW = 3; + public static final int VAO_PRESCENE = 4; + public static final int VAO_COUNT = 5; + + final DynamicModelVAO[] dynamicModelVaos = new DynamicModelVAO[VAO_COUNT]; + + void initBuffers() { + for (int i = 0; i < VAO_COUNT; i++) { + dynamicModelVaos[i] = new DynamicModelVAO("DynamicModelVAO_" + i, i == VAO_OPAQUE || i == VAO_PLAYER || i == VAO_SHADOW); + if (dynamicModelVaos[i].getVao() == 0) + dynamicModelVaos[i].initialize(); + } + } + + void destroy() { + for (int i = 0; i < VAO_COUNT; i++) { + if (dynamicModelVaos[i] != null) + dynamicModelVaos[i].destroy(); + dynamicModelVaos[i] = null; + } + } + + void map() { + for (int i = 0; i < VAO_COUNT; i++) + dynamicModelVaos[i].map(); + } + + int obtainDrawIndex(int type) { + return dynamicModelVaos[type].obtainDrawIndex(); + } + + void unmap() { + for (int i = 0; i < VAO_COUNT; i++) + dynamicModelVaos[i].unmap(); + } +} diff --git a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java index a0b1b3ba06..a7818b07f5 100644 --- a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java +++ b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java @@ -30,10 +30,10 @@ import static net.runelite.api.Perspective.*; import static net.runelite.api.hooks.DrawCallbacks.*; import static rs117.hd.HdPlugin.PROCESSOR_COUNT; -import static rs117.hd.renderer.zone.WorldViewContext.VAO_ALPHA; -import static rs117.hd.renderer.zone.WorldViewContext.VAO_OPAQUE; -import static rs117.hd.renderer.zone.WorldViewContext.VAO_PLAYER; -import static rs117.hd.renderer.zone.WorldViewContext.VAO_SHADOW; +import static rs117.hd.renderer.zone.FrameContext.VAO_ALPHA; +import static rs117.hd.renderer.zone.FrameContext.VAO_OPAQUE; +import static rs117.hd.renderer.zone.FrameContext.VAO_PLAYER; +import static rs117.hd.renderer.zone.FrameContext.VAO_SHADOW; import static rs117.hd.utils.MathUtils.*; @Slf4j @@ -184,7 +184,7 @@ public void drawTemp( int zz = (z >> 10) + offset; Zone zone = ctx.zones[zx][zz]; - if (!zone.initialized) + if (!zone.initialized || zone.fadingAlpha > 1.0f) return; final StreamingContext streamingContext = context(renderThreadId); @@ -193,18 +193,36 @@ public void drawTemp( ctx.uboWorldViewStruct.project(objectWorldPos); final int uuid; + final float modelFade; if (r instanceof DynamicObject) { int id = tileObject.getId(); int impostorId = root.sceneContext.animatedDynamicObjectImpostors.getOrDefault(id, id); uuid = ModelHash.packUuid(ModelHash.getType(tileObject.getHash()), impostorId); // Cull dynamic models based on detail draw distance - float squaredDistance = renderer.sceneCamera.squaredDistanceTo(objectWorldPos[0], objectWorldPos[1], objectWorldPos[2]); - int detailDrawDistanceTiles = plugin.configDetailDrawDistance * LOCAL_TILE_SIZE; - if (squaredDistance > detailDrawDistanceTiles * detailDrawDistanceTiles && modelOverrideManager.allowDetailCulling(uuid)) - return; + if (modelOverrideManager.allowDetailCulling(uuid)) { + final int detailDrawDistanceTiles = plugin.configDetailDrawDistance * LOCAL_TILE_SIZE; + final float squaredDistance = renderer.sceneCamera.squaredDistanceTo( + objectWorldPos[0], + objectWorldPos[1], + objectWorldPos[2] + ); + final float detailDrawDistanceTilesSquared = detailDrawDistanceTiles * detailDrawDistanceTiles; + if (squaredDistance > detailDrawDistanceTilesSquared) + return; + + // Fade dynamic models that are close to the detail draw distance so that they don't pop in/out + final float fadeRange = 8.0f * LOCAL_TILE_SIZE; + final float fadeEndSq = detailDrawDistanceTiles * detailDrawDistanceTiles; + final float fadeStart = max(0.0f, detailDrawDistanceTiles - fadeRange); + final float fadeStartSq = fadeStart * fadeStart; + modelFade = saturate((squaredDistance - fadeStartSq) / (fadeEndSq - fadeStartSq)); + } else { + modelFade = 0; + } } else { uuid = ModelHash.generateUuid(client, tileObject.getHash(), r); + modelFade = 0; } // Hide everything outside the current area if area hiding is enabled @@ -255,7 +273,8 @@ public void drawTemp( z & 1023 ) : null; - final int drawIndex = renderThreadId != -1 ? -1 : ctx.obtainDrawIndex(r instanceof Player ? VAO_PLAYER : VAO_OPAQUE); + final int drawIndex = + renderThreadId != -1 ? -1 : renderer.frameContext().obtainDrawIndex(r instanceof Player ? VAO_PLAYER : VAO_OPAQUE); final boolean isModelPartiallyVisible = sceneManager.isRoot(ctx) && modelClassification == 0; final AsyncCachedModel asyncModelCache = obtainAvailableAsyncCachedModel(m); if (asyncModelCache != null) { @@ -271,6 +290,7 @@ public void drawTemp( alphaModel, isModelPartiallyVisible, drawIndex, + modelFade, orient, x, y, z, this::uploadTempModelAsync @@ -290,6 +310,7 @@ public void drawTemp( isModelPartiallyVisible, -1, drawIndex, + modelFade, orient, x, y, z ); @@ -306,6 +327,7 @@ private void uploadTempModelAsync( Zone.AlphaModel alphaModel, boolean isModelPartiallyVisible, int drawIndex, + float modelFade, int orientation, int x, int y, int z ) { @@ -322,6 +344,7 @@ private void uploadTempModelAsync( isModelPartiallyVisible, -1, drawIndex, + modelFade, orientation, x, y, z ); @@ -340,6 +363,7 @@ public void uploadTempModel( boolean isModelPartiallyVisible, int vaoType, int drawIndex, + float modelFade, int orient, float x, float y, float z ) { @@ -393,12 +417,26 @@ public void uploadTempModel( (!sceneManager.isRoot(ctx) || zone != null && zone.inShadowFrustum) ) { final DynamicModelVAO.View shadowView = ctx.beginDraw(VAO_SHADOW, culledFaces.length); + final int shadowModelIdx = SceneUploader.writeDynamicModelData( + shadowView.tboM, + x, + y, + z, + modelFade, + m, + modelOverride, + ctx, + zone + ); sceneUploader.uploadTempModel( culledFaces, m, modelOverride, + x, y, z, preOrientation, orient, + shadowModelIdx, + shadowModelIdx, true, shadowView, shadowView @@ -419,12 +457,30 @@ public void uploadTempModel( final DynamicModelVAO.View opaqueView = ctx.beginDraw(vaoType, drawIndex, opaqueFaceCount); final DynamicModelVAO.View alphaView = alphaFaceCount > 0 ? ctx.beginDraw(VAO_ALPHA, alphaFaceCount) : opaqueView; + final int opaqueModelIdx = SceneUploader.writeDynamicModelData( + opaqueView.tboM, + x, + y, + z, + modelFade, + m, + modelOverride, + ctx, + zone + ); + final int alphaModelIdx = alphaFaceCount > 0 ? + SceneUploader.writeDynamicModelData(alphaView.tboM, x, y, z, modelFade, m, modelOverride, ctx, zone) : + opaqueModelIdx; + sceneUploader.uploadTempModel( visibleFaces, m, modelOverride, + x, y, z, preOrientation, orient, + opaqueModelIdx, + alphaModelIdx, isSquashed, opaqueView, alphaView @@ -460,7 +516,8 @@ public void ensureAsyncUploadsComplete(@Nullable Zone zone) { while (!pending.isEmpty()) { pendingModel = pending.get(idx); - if (pendingModel.isQueued() && pendingModel.canStart() && pendingModel.processModel()) { + if (pendingModel.isQueued() && pendingModel.canStart()) { + pendingModel.waitForCompletion(); // Await will pick up work items to process synchronously if it hasn't already started pending.remove(idx); hasStolen = true; } diff --git a/src/main/java/rs117/hd/renderer/zone/SceneManager.java b/src/main/java/rs117/hd/renderer/zone/SceneManager.java index d29791bf58..537f6745f3 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneManager.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneManager.java @@ -36,12 +36,11 @@ import rs117.hd.utils.collections.Int2IntHashMap; import rs117.hd.utils.collections.PooledArrayType; import rs117.hd.utils.jobs.GenericJob; +import rs117.hd.utils.jobs.Job; import static net.runelite.api.Constants.*; -import static net.runelite.api.Perspective.SCENE_SIZE; +import static rs117.hd.HdPlugin.PROCESSOR_COUNT; import static rs117.hd.HdPlugin.checkGLErrors; -import static rs117.hd.renderer.zone.WorldViewContext.DYNAMIC_MODEL_VAO_POOL; -import static rs117.hd.renderer.zone.WorldViewContext.DYNAMIC_MODEL_VAO_STAGING_POOL; import static rs117.hd.utils.MathUtils.*; @Slf4j @@ -101,6 +100,7 @@ public class SceneManager { private ZoneSceneContext nextSceneContext; private Zone[][] nextZones; private final List sortedZones = new ArrayList<>(); + private final Job[] roundRobinDependencies = new Job[PROCESSOR_COUNT]; private boolean reloadRequested; public boolean isZoneStreamingEnabled() { @@ -122,7 +122,7 @@ public ZoneSceneContext getSceneContext() { public boolean isRoot(WorldViewContext context) { return root == context; } public WorldViewContext getContext(Scene scene) { - return getContext(scene.getWorldViewId()); + return scene != null ? getContext(scene.getWorldViewId()) : null; } public WorldViewContext getContext(WorldView wv) { @@ -153,9 +153,6 @@ public void destroy() { subs[i] = null; } - DYNAMIC_MODEL_VAO_STAGING_POOL.destroy(); - DYNAMIC_MODEL_VAO_POOL.destroy(); - Zone.freeZones(nextZones); nextZones = null; if (nextSceneContext != null) @@ -598,17 +595,26 @@ public synchronized void loadScene(WorldView worldView, Scene scene) { } } - long timeMs = System.currentTimeMillis(); + int robinIdx = 0; + int robinSize = (int) max(1, PROCESSOR_COUNT * config.cpuUsageLimit().threadRatio * 0.5f); + Arrays.fill(roundRobinDependencies, 0, robinSize, generateSceneDataTask); + for (SortedZone sorted : sortedZones) { Zone newZone = injector.getInstance(Zone.class); newZone.dirty = sorted.zone.dirty; if (staggerLoad) { + if (!sorted.zone.cull) + newZone.fadingAlpha = saturate(sorted.dist / 15.0f) * 3.0f; // Fade in new chunks that are appearing out of the fog + // Reuse the old zone while uploading a correct one sorted.zone.cull = false; sorted.zone.uploadJob = ZoneUploadJob - .build(ctx, nextSceneContext, newZone, false, sorted.x, sorted.z); - sorted.zone.uploadJob.revealAfterTimestampMs = - timeMs + ceil(clamp(sorted.dist / 15.0f, 0.25f, 1.5f) * 1000.0f); + .build(ctx, nextSceneContext, newZone, false, sorted.x, sorted.z) + .queue(ctx.streamingGroup, roundRobinDependencies[robinIdx]); + + // Round Robin the streaming in zones to avoid saturating the Job queue + roundRobinDependencies[robinIdx] = sorted.zone.uploadJob; + robinIdx = (robinIdx + 1) % robinSize; } else { nextZones[sorted.x][sorted.z] = newZone; ZoneUploadJob @@ -617,6 +623,7 @@ public synchronized void loadScene(WorldView worldView, Scene scene) { } sorted.free(); } + Arrays.fill(roundRobinDependencies, 0, robinSize, null); sortedZones.clear(); root.loadTime = sw.elapsed(TimeUnit.NANOSECONDS); @@ -730,8 +737,6 @@ public void swapScene(Scene scene) { nextSceneContext = null; if (isFirst) { - root.initBuffers(); - // Load all pre-existing sub scenes on the first scene load for (WorldEntity subEntity : client.getTopLevelWorldView().worldEntities()) { WorldView sub = subEntity.getWorldView(); @@ -779,6 +784,7 @@ private void loadSubScene(WorldView worldView, Scene scene) { .build(ctx, sceneContext, ctx.zones[x][z], true, x, z) .queue(ctx.sceneLoadGroup); + ctx.buildBoatDisplacement(); ctx.loadTime = sw.elapsed(TimeUnit.NANOSECONDS); } @@ -788,7 +794,6 @@ private void swapSubScene(Scene scene) { return; Stopwatch sw = Stopwatch.createStarted(); - ctx.initBuffers(); ctx.sceneLoadGroup.complete(); ctx.uploadTime = sw.elapsed(TimeUnit.NANOSECONDS); ctx.sceneSwapTime = sw.elapsed(TimeUnit.NANOSECONDS); diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index 5bd076799b..ca1939eda1 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -24,6 +24,7 @@ */ package rs117.hd.renderer.zone; +import java.nio.IntBuffer; import javax.annotation.Nullable; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; @@ -45,6 +46,7 @@ import rs117.hd.scene.water_types.WaterType; import rs117.hd.utils.HDUtils; import rs117.hd.utils.ModelHash; +import rs117.hd.utils.buffer.GLMappedBufferIntWriter; import rs117.hd.utils.buffer.GpuIntBuffer; import rs117.hd.utils.collections.ConcurrentPool; import rs117.hd.utils.collections.PooledArrayType; @@ -133,7 +135,8 @@ public interface OnBeforeProcessTileFunc { private final int[] modelNormals = new int[9]; private final short[][] tileNormals = new short[4][3]; - private int[] modelVertices; + private int[] intModelVertices; + private float[] floatModelVertices; public int tempModelAlphaFaces = 0; private final PooledObjectArray faceOverrides = new PooledObjectArray<>(); @@ -146,6 +149,7 @@ public interface OnBeforeProcessTileFunc { private final GpuIntBuffer zoneVboO = new GpuIntBuffer(false); private final GpuIntBuffer zoneVboA = new GpuIntBuffer(false); private final GpuIntBuffer zoneTboF = new GpuIntBuffer(false); + private final GpuIntBuffer zoneTboM = new GpuIntBuffer(false); // Lazily initialized staging buffers public VertexWriteCache.Collection writeCache; @@ -169,16 +173,23 @@ public void clear() { currentScene = null; onBeforeProcessTile = null; - PooledArrayType.INT.release(modelVertices); - modelVertices = null; + PooledArrayType.INT.release(intModelVertices); + PooledArrayType.FLOAT.release(floatModelVertices); + + intModelVertices = null; + floatModelVertices = null; faceOverrides.release(); faceMaterials.release(); faceUVTypes.release(); } - private void ensureVerticesAllocated(int vertexCount) { - modelVertices = PooledArrayType.INT.ensureCapacity(modelVertices, vertexCount * 3); + private void ensureVerticesAllocated(boolean allocateInts, boolean allocateFloats, int vertexCount) { + if (allocateInts) + intModelVertices = PooledArrayType.INT.ensureCapacity(intModelVertices, vertexCount * 3); + + if (allocateFloats) + floatModelVertices = PooledArrayType.FLOAT.ensureCapacity(floatModelVertices, vertexCount * 3); } public void estimateZoneSize(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws InterruptedException { @@ -214,6 +225,7 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws var vb = zone.vboO != null ? zoneVboO.setBuffer(zone.vboO.mapped()) : null; var ab = zone.vboA != null ? zoneVboA.setBuffer(zone.vboA.mapped()) : null; var fb = zone.tboF != null ? zoneTboF.setBuffer(zone.tboF.mapped()) : null; + var md = zone.tboM != null ? zoneTboM.setBuffer(zone.tboM.mapped()) : null; assert zone.tboF != null; roofIds.length = 0; @@ -238,13 +250,13 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws this.level = z; if (z == 0) { - uploadZoneLevel(ctx, zone, mzx, mzz, 0, false, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 0, true, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 1, true, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 2, true, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 3, true, vb, ab, fb); + uploadZoneLevel(ctx, zone, mzx, mzz, 0, false, vb, ab, fb, md); + uploadZoneLevel(ctx, zone, mzx, mzz, 0, true, vb, ab, fb, md); + uploadZoneLevel(ctx, zone, mzx, mzz, 1, true, vb, ab, fb, md); + uploadZoneLevel(ctx, zone, mzx, mzz, 2, true, vb, ab, fb, md); + uploadZoneLevel(ctx, zone, mzx, mzz, 3, true, vb, ab, fb, md); } else { - uploadZoneLevel(ctx, zone, mzx, mzz, z, false, vb, ab, fb); + uploadZoneLevel(ctx, zone, mzx, mzz, z, false, vb, ab, fb, md); } if (vb != null) @@ -272,16 +284,17 @@ private void uploadZoneLevel( boolean visbelow, GpuIntBuffer vb, GpuIntBuffer ab, - GpuIntBuffer fb + GpuIntBuffer fb, + GpuIntBuffer md ) throws InterruptedException { int ridx = 0; // upload the roofs and save their positions - for (int i = 0; i < roofIds.length; ++i ) { + for (int i = 0; i < roofIds.length; ++i) { final int id = roofIds.array[i]; int pos = vb != null ? vb.position() : 0; - uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, id, visbelow, vb, ab, fb); + uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, id, visbelow, vb, ab, fb, md); int endpos = vb != null ? vb.position() : 0; @@ -294,7 +307,7 @@ private void uploadZoneLevel( } // upload everything else - uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, 0, visbelow, vb, ab, fb); + uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, 0, visbelow, vb, ab, fb, md); } private void uploadZoneLevelRoof( @@ -307,7 +320,8 @@ private void uploadZoneLevelRoof( boolean visbelow, GpuIntBuffer vb, GpuIntBuffer ab, - GpuIntBuffer fb + GpuIntBuffer fb, + GpuIntBuffer md ) throws InterruptedException { this.basex = (mzx - (ctx.sceneOffset >> 3)) << 10; this.basez = (mzz - (ctx.sceneOffset >> 3)) << 10; @@ -341,7 +355,7 @@ private void uploadZoneLevelRoof( this.rid = rid; if (onBeforeProcessTile != null) onBeforeProcessTile.invoke(t, false); - uploadZoneTile(ctx, zone, t, false, false, vb, ab, fb); + uploadZoneTile(ctx, zone, t, false, false, vb, ab, fb, md); } } } @@ -371,7 +385,7 @@ private void uploadZoneWater( if (onBeforeProcessTile != null) onBeforeProcessTile.invoke(t, false); - uploadZoneTile(ctx, zone, t, false, true, vb, null, fb); + uploadZoneTile(ctx, zone, t, false, true, vb, null, fb, null); } } } @@ -496,7 +510,8 @@ private void uploadZoneTile( boolean onlyWaterSurface, GpuIntBuffer vertexBuffer, GpuIntBuffer alphaBuffer, - GpuIntBuffer textureBuffer + GpuIntBuffer textureBuffer, + GpuIntBuffer modelBuffer ) { var tilePoint = t.getSceneLocation(); int tileExX = tilePoint.getX() + ctx.sceneOffset; @@ -528,11 +543,11 @@ private void uploadZoneTile( uploadTileModel(ctx, t, model, onlyWaterSurface, tileExX, tileExY, tileZ, basex, basez, vertexBuffer, textureBuffer); if (!onlyWaterSurface) - uploadZoneTileRenderables(ctx, zone, t, tileExX, tileExY, tileZ, vertexBuffer, alphaBuffer, textureBuffer); + uploadZoneTileRenderables(ctx, zone, t, tileExX, tileExY, tileZ, vertexBuffer, alphaBuffer, textureBuffer, modelBuffer); Tile bridge = t.getBridge(); if (bridge != null) - uploadZoneTile(ctx, zone, bridge, true, onlyWaterSurface, vertexBuffer, alphaBuffer, textureBuffer); + uploadZoneTile(ctx, zone, bridge, true, onlyWaterSurface, vertexBuffer, alphaBuffer, textureBuffer, modelBuffer); } private void uploadZoneTileRenderables( @@ -542,7 +557,8 @@ private void uploadZoneTileRenderables( int tileExX, int tileExY, int tileZ, GpuIntBuffer vertexBuffer, GpuIntBuffer alphaBuffer, - GpuIntBuffer textureBuffer + GpuIntBuffer textureBuffer, + GpuIntBuffer modelBuffer ) { WallObject wallObject = t.getWallObject(); if (wallObject != null && renderCallbackManager.drawObject(ctx.scene, wallObject)) { @@ -567,7 +583,8 @@ private void uploadZoneTileRenderables( tileExX, tileExY, tileZ, vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); Renderable renderable2 = wallObject.getRenderable2(); @@ -590,7 +607,8 @@ private void uploadZoneTileRenderables( tileExX, tileExY, tileZ, vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } @@ -618,7 +636,8 @@ private void uploadZoneTileRenderables( tileExX, tileExY, tileZ, vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); Renderable renderable2 = decorativeObject.getRenderable2(); @@ -641,7 +660,8 @@ private void uploadZoneTileRenderables( tileExX, tileExY, tileZ, vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } @@ -664,7 +684,8 @@ private void uploadZoneTileRenderables( tileExX, tileExY, tileZ, vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } @@ -699,7 +720,8 @@ private void uploadZoneTileRenderables( tileExX, tileExY, tileZ, vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } } @@ -737,6 +759,8 @@ private void estimateRenderableSize(Zone z, Renderable r, ModelOverride modelOve mightHaveTransparency |= transparencies != null || isVanillaTextured || modelTransparency != 0; if (mightHaveTransparency) z.sizeA += faceCount; + + z.sizeM++; } private void uploadZoneRenderable( @@ -758,7 +782,8 @@ private void uploadZoneRenderable( int tileExX, int tileExY, int tileZ, GpuIntBuffer opaqueBuffer, GpuIntBuffer alphaBuffer, - GpuIntBuffer textureBuffer + GpuIntBuffer textureBuffer, + GpuIntBuffer modelBuffer ) { Model model; if (r instanceof Model) { @@ -787,13 +812,14 @@ private void uploadZoneRenderable( int alphaStart = alphaBuffer != null ? alphaBuffer.position() : 0; try { uploadStaticModel( - ctx, tile, model, modelOverride, uuid, + ctx, tile, model, modelOverride, zone, uuid, preOrientation, orient, x - basex, y, z - basez, tileExX, tileExY, tileZ, opaqueBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } catch (Throwable ex) { try (var gamevals = gamevalManager.obtainHandle()) { @@ -830,6 +856,7 @@ private void uploadZoneRenderable( materialManager, zone.glVaoA, zone.tboF.getTexId(), + zone.tboM.getTexId(), model, modelOverride, alphaStart, alphaEnd, x - basex, y, z - basez, lx, lz, ux, uz, @@ -1068,104 +1095,112 @@ private void uploadTilePaint( final var vb = writeCache.getVertexBuffer(); final var tb = writeCache.getTextureBuffer(); - int texturedFaceIdx = tb.putFace( + int texturedFaceIdx = tb.putStaticFace( neColor, nwColor, seColor, neMaterialData, nwMaterialData, seMaterialData, neTerrainData, nwTerrainData, seTerrainData ); - vb.putStaticVertex( + vb.putVertex( lx2, neHeight, lz2, uvx, uvy, 0, neNormals[0], neNormals[1], neNormals[2], - texturedFaceIdx, false + texturedFaceIdx, false, 0 ); - vb.putStaticVertex( + vb.putVertex( lx3, nwHeight, lz3, uvx - uvcos, uvy - uvsin, 0, nwNormals[0], nwNormals[1], nwNormals[2], - texturedFaceIdx, false + texturedFaceIdx, false, 0 ); - vb.putStaticVertex( + vb.putVertex( lx1, seHeight, lz1, uvx + uvsin, uvy - uvcos, 0, seNormals[0], seNormals[1], seNormals[2], - texturedFaceIdx, false + texturedFaceIdx, false, 0 + ); + + texturedFaceIdx = tb.findStaticFace( + swColor, seColor, nwColor, + swMaterialData, seMaterialData, nwMaterialData, + swTerrainData, seTerrainData, nwTerrainData ); boolean isDoubleSided = !onlyWaterSurface && (tileZ != 0 || override.doubleSidedFaces); if (isDoubleSided) { - vb.putStaticVertex( + vb.putVertex( lx1, seHeight, lz1, uvx + uvsin, uvy - uvcos, 0, -seNormals[0], -seNormals[1], -seNormals[2], - texturedFaceIdx, true + texturedFaceIdx, true, 0 ); - vb.putStaticVertex( + vb.putVertex( lx3, nwHeight, lz3, uvx - uvcos, uvy - uvsin, 0, -nwNormals[0], -nwNormals[1], -nwNormals[2], - texturedFaceIdx, true + texturedFaceIdx, true, 0 ); - vb.putStaticVertex( + vb.putVertex( lx2, neHeight, lz2, uvx, uvy, 0, -neNormals[0], -neNormals[1], -neNormals[2], - texturedFaceIdx, true + texturedFaceIdx, true, 0 ); } - texturedFaceIdx = tb.putFace( - swColor, seColor, nwColor, - swMaterialData, seMaterialData, nwMaterialData, - swTerrainData, seTerrainData, nwTerrainData - ); + if (texturedFaceIdx == -1) { + texturedFaceIdx = tb.putStaticFace( + swColor, seColor, nwColor, + swMaterialData, seMaterialData, nwMaterialData, + swTerrainData, seTerrainData, nwTerrainData + ); + } - vb.putStaticVertex( + vb.putVertex( lx0, swHeight, lz0, uvx - uvcos + uvsin, uvy - uvsin - uvcos, 0, swNormals[0], swNormals[1], swNormals[2], - texturedFaceIdx, false + texturedFaceIdx, false, 0 ); - vb.putStaticVertex( + vb.putVertex( lx1, seHeight, lz1, uvx + uvsin, uvy - uvcos, 0, seNormals[0], seNormals[1], seNormals[2], - texturedFaceIdx, false + texturedFaceIdx, false, 0 ); - vb.putStaticVertex( + vb.putVertex( lx3, nwHeight, lz3, uvx - uvcos, uvy - uvsin, 0, nwNormals[0], nwNormals[1], nwNormals[2], - texturedFaceIdx, false + texturedFaceIdx, false, 0 ); if (isDoubleSided) { - vb.putStaticVertex( + vb.putVertex( lx3, nwHeight, lz3, uvx - uvcos, uvy - uvsin, 0, -nwNormals[0], -nwNormals[1], -nwNormals[2], - texturedFaceIdx, true + texturedFaceIdx, true, 0 ); - vb.putStaticVertex( + vb.putVertex( lx1, seHeight, lz1, uvx + uvsin, uvy - uvcos, 0, -seNormals[0], -seNormals[1], -seNormals[2], - texturedFaceIdx, true + texturedFaceIdx, true, 0 ); - vb.putStaticVertex( + vb.putVertex( lx0, swHeight, lz0, uvx - uvcos + uvsin, uvy - uvsin - uvcos, 0, -swNormals[0], -swNormals[1], -swNormals[2], - texturedFaceIdx, true + texturedFaceIdx, true, 0 ); } @@ -1437,73 +1472,141 @@ private void uploadTileModel( final VertexWriteCache vb = writeCache.getVertexBuffer(); final VertexWriteCache tb = writeCache.getTextureBuffer(); - int texturedFaceIdx = tb.putFace( + int texturedFaceIdx = tb.findStaticFace( colorA, colorB, colorC, materialDataA, materialDataB, materialDataC, terrainDataA, terrainDataB, terrainDataC ); - vb.putStaticVertex( + if (texturedFaceIdx == -1) { + texturedFaceIdx = tb.putStaticFace( + colorA, colorB, colorC, + materialDataA, materialDataB, materialDataC, + terrainDataA, terrainDataB, terrainDataC + ); + } + + vb.putVertex( lx0, ly0, lz0, uvAx, uvAy, 0, normalsA[0], normalsA[1], normalsA[2], - texturedFaceIdx, false + texturedFaceIdx, false, 0 ); - vb.putStaticVertex( + vb.putVertex( lx1, ly1, lz1, uvBx, uvBy, 0, normalsB[0], normalsB[1], normalsB[2], - texturedFaceIdx, false + texturedFaceIdx, false, 0 ); - vb.putStaticVertex( + vb.putVertex( lx2, ly2, lz2, uvCx, uvCy, 0, normalsC[0], normalsC[1], normalsC[2], - texturedFaceIdx, false + texturedFaceIdx, false, 0 ); boolean isDoubleSided = !onlyWaterSurface && (tileZ != 0 || override.doubleSidedFaces); if (isDoubleSided) { - vb.putStaticVertex( + vb.putVertex( lx2, ly2, lz2, uvCx, uvCy, 0, -normalsC[0], -normalsC[1], -normalsC[2], - texturedFaceIdx, true + texturedFaceIdx, true, 0 ); - vb.putStaticVertex( + vb.putVertex( lx1, ly1, lz1, uvBx, uvBy, 0, -normalsB[0], -normalsB[1], -normalsB[2], - texturedFaceIdx, true + texturedFaceIdx, true, 0 ); - vb.putStaticVertex( + vb.putVertex( lx0, ly0, lz0, uvAx, uvAy, 0, -normalsA[0], -normalsA[1], -normalsA[2], - texturedFaceIdx, true + texturedFaceIdx, true, 0 ); } } writeCache.release(); } + private static void writeModelData( + IntBuffer modelBuffer, + float x, + float y, + float z, + float fade, + Model model, + ModelOverride override, + WorldViewContext viewCtx, + Zone zone + ) { + // Model Data Struct: + // int WorldViewIdx + // int Flags + // vec3 Position + // int Height + // float Fade + + modelBuffer + .put(viewCtx != null && viewCtx.uboWorldViewStruct != null ? viewCtx.uboWorldViewStruct.worldViewIdx + 1 : 0) + .put(viewCtx != null ? 0 : 1) + .put(Float.floatToIntBits(x)) + .put(Float.floatToIntBits(y)) + .put(Float.floatToIntBits(z)) + .put(model.getModelHeight()) + .put(Float.floatToIntBits(viewCtx != null ? saturate(max(zone.fadingAlpha, fade)) : -1.0f)); + } + + public static int writeStaticModelData(IntBuffer modelBuffer, int x, int y, int z, Model model, ModelOverride override, Zone zone) { + final int modelDataSizeInts = Zone.MODEL_DATA_SIZE / Integer.BYTES; + final int modelIdx = modelBuffer.position() / modelDataSizeInts; + + writeModelData(modelBuffer, x, y, z, 0.0f, model, override, null, zone); + + assert modelBuffer.position() % modelDataSizeInts == 0; + return modelIdx + 1; + } + + public static int writeDynamicModelData( + GLMappedBufferIntWriter.ReservedView view, + float x, + float y, + float z, + float fade, + Model model, + ModelOverride override, + WorldViewContext viewCtx, + Zone zone + ) { + final int modelDataSizeInts = Zone.MODEL_DATA_SIZE / Integer.BYTES; + final int modelIdx = view.getBufferOffsetInts() / modelDataSizeInts; + + writeModelData(view.getBuffer(), x, y, z, fade, model, override, viewCtx, zone); + + assert view.getBufferOffsetInts() % modelDataSizeInts == 0; + return modelIdx + 1; + } + // scene upload private int uploadStaticModel( ZoneSceneContext ctx, Tile tile, Model model, ModelOverride modelOverride, + Zone zone, int uuid, int preOrientation, int orientation, int x, int y, int z, int tileExX, int tileExY, int tileZ, GpuIntBuffer opaqueBuffer, GpuIntBuffer alphaBuffer, - GpuIntBuffer textureBuffer + GpuIntBuffer textureBuffer, + GpuIntBuffer modelBuffer ) { if (writeCache == null) writeCache = new VertexWriteCache.Collection(); @@ -1546,7 +1649,7 @@ private int uploadStaticModel( orientCos = COSINE[orientation]; } - ensureVerticesAllocated(vertexCount); + ensureVerticesAllocated(true, false, vertexCount); for (int v = 0, vertexOffset = 0; v < vertexCount; ++v) { int vx = (int) vertexX[v]; @@ -1581,9 +1684,9 @@ private int uploadStaticModel( vy = (int) mix(h, vy, blend); } - modelVertices[vertexOffset++] = vx; - modelVertices[vertexOffset++] = vy; - modelVertices[vertexOffset++] = vz; + intModelVertices[vertexOffset++] = vx; + intModelVertices[vertexOffset++] = vy; + intModelVertices[vertexOffset++] = vz; } boolean isVanillaTextured = faceTextures != null; @@ -1594,6 +1697,15 @@ private int uploadStaticModel( final Material baseMaterial = modelOverride.baseMaterial; final Material textureMaterial = modelOverride.textureMaterial; + int modelX = x, modelZ = z; + if (modelOverride.positionTileSnapping > 0) { + float snapping = modelOverride.positionTileSnapping * LOCAL_HALF_TILE_SIZE; + modelX = (int) (floor(modelX / snapping) * snapping); + modelZ = (int) (floor(modelZ / snapping) * snapping); + } + + final int modelIdx = writeStaticModelData(modelBuffer.getBuffer(), modelX, y, modelZ, model, modelOverride, zone); + int len = 0; for (int face = 0; face < faceCount; ++face) { int color1 = color1s[face]; @@ -1650,19 +1762,19 @@ private int uploadStaticModel( final int triangleC = indices3[face]; int vertexOffset = triangleA * 3; - final int vx1 = modelVertices[vertexOffset]; - final int vy1 = modelVertices[vertexOffset + 1]; - final int vz1 = modelVertices[vertexOffset + 2]; + final int vx1 = intModelVertices[vertexOffset]; + final int vy1 = intModelVertices[vertexOffset + 1]; + final int vz1 = intModelVertices[vertexOffset + 2]; vertexOffset = triangleB * 3; - final int vx2 = modelVertices[vertexOffset]; - final int vy2 = modelVertices[vertexOffset + 1]; - final int vz2 = modelVertices[vertexOffset + 2]; + final int vx2 = intModelVertices[vertexOffset]; + final int vy2 = intModelVertices[vertexOffset + 1]; + final int vz2 = intModelVertices[vertexOffset + 2]; vertexOffset = triangleC * 3; - final int vx3 = modelVertices[vertexOffset]; - final int vy3 = modelVertices[vertexOffset + 1]; - final int vz3 = modelVertices[vertexOffset + 2]; + final int vx3 = intModelVertices[vertexOffset]; + final int vy3 = intModelVertices[vertexOffset + 1]; + final int vz3 = intModelVertices[vertexOffset + 2]; boolean keepShading = isTextured; if (isTextured) { @@ -1722,9 +1834,11 @@ private int uploadStaticModel( if (faceColorIndex != -1) { int color = tileModel.getTriangleColorA()[faceColorIndex]; if (color != HIDDEN_HSL) { - var override = ctx.getTileOverride(tileZ, tileExX, tileExY, + var override = ctx.getTileOverride( + tileZ, tileExX, tileExY, faceOverride.inheritTileColorType == InheritTileColorType.OVERLAY ? - TILE_OVERRIDE_OVERLAY : TILE_OVERRIDE_UNDERLAY); + TILE_OVERRIDE_OVERLAY : TILE_OVERRIDE_UNDERLAY + ); color = override.modifyColor(color); color1 = color2 = color3 = color; @@ -1826,53 +1940,52 @@ private int uploadStaticModel( color2 |= packedAlphaBiasHsl; color3 |= packedAlphaBiasHsl; - final int texturedFaceIdx = tb.putFace( - color1, color2, color3, - materialData, materialData, materialData, - 0, 0, 0 - ); + // Check if we can reuse an existing model face to dedup the amount of faces written + int texturedFaceIdx = tb.findModelFace(color1, color2, color3, materialData); + if (texturedFaceIdx == -1) + texturedFaceIdx = tb.putModelFace(color1, color2, color3, materialData); - vb.putStaticVertex( + vb.putVertex( vx1, vy1, vz1, faceUVs[0], faceUVs[1], faceUVs[2], modelNormals[0], modelNormals[1], modelNormals[2], - texturedFaceIdx, false + texturedFaceIdx, false, modelIdx ); - vb.putStaticVertex( + vb.putVertex( vx2, vy2, vz2, faceUVs[4], faceUVs[5], faceUVs[6], modelNormals[3], modelNormals[4], modelNormals[5], - texturedFaceIdx, false + texturedFaceIdx, false, modelIdx ); - vb.putStaticVertex( + vb.putVertex( vx3, vy3, vz3, faceUVs[8], faceUVs[9], faceUVs[10], modelNormals[6], modelNormals[7], modelNormals[8], - texturedFaceIdx, false + texturedFaceIdx, false, modelIdx ); if (faceOverride.doubleSidedFaces || material.doubleSidedFaces) { - vb.putStaticVertex( + vb.putVertex( vx3, vy3, vz3, faceUVs[8], faceUVs[9], faceUVs[10], -modelNormals[6], -modelNormals[7], -modelNormals[8], - texturedFaceIdx, true + texturedFaceIdx, true, modelIdx ); - vb.putStaticVertex( + vb.putVertex( vx2, vy2, vz2, faceUVs[4], faceUVs[5], faceUVs[6], -modelNormals[3], -modelNormals[4], -modelNormals[5], - texturedFaceIdx, true + texturedFaceIdx, true, modelIdx ); - vb.putStaticVertex( + vb.putVertex( vx1, vy1, vz1, faceUVs[0], faceUVs[1], faceUVs[2], -modelNormals[0], -modelNormals[1], -modelNormals[2], - texturedFaceIdx, true + texturedFaceIdx, true, modelIdx ); } len += 3; @@ -1913,7 +2026,7 @@ public boolean preprocessTempModel( orientCosf = COSINE[orientation] / 65536f; } - ensureVerticesAllocated(vertexCount); + ensureVerticesAllocated(true, true, vertexCount); boolean shouldSort = true; boolean allVertsVisible = true; @@ -1952,15 +2065,18 @@ public boolean preprocessTempModel( visibility[v] = allVertsVisible = false; } - modelVertices[vertexOffset] = Float.floatToRawIntBits(vertexX); + floatModelVertices[vertexOffset] = vertexX; + intModelVertices[vertexOffset] = float16(vertexX - x); modelProjected[vertexOffset] = pX / pZ; vertexOffset++; - modelVertices[vertexOffset] = Float.floatToRawIntBits(vertexY); + floatModelVertices[vertexOffset] = vertexY; + intModelVertices[vertexOffset] = float16(vertexY - y); modelProjected[vertexOffset] = pY / pZ; vertexOffset++; - modelVertices[vertexOffset] = Float.floatToRawIntBits(vertexZ); + floatModelVertices[vertexOffset] = vertexZ; + intModelVertices[vertexOffset] = float16(vertexZ - z); modelProjected[vertexOffset] = pZ; vertexOffset++; @@ -2136,8 +2252,11 @@ public void uploadTempModel( PrimitiveCharArray faces, Model model, ModelOverride modelOverride, + float x, float y, float z, int preOrientation, int orientation, + int opaqueModelIdx, + int alphaModelIdx, boolean isShadow, DynamicModelVAO.View opaqueView, DynamicModelVAO.View alphaView @@ -2147,8 +2266,8 @@ public void uploadTempModel( writeCache.setOutputBuffers( opaqueView.vbo.getBuffer(), alphaView.vbo.getBuffer(), - opaqueView.tbo.getBuffer(), - alphaView.tbo.getBuffer() + opaqueView.tboF.getBuffer(), + alphaView.tboF.getBuffer() ); final int[] indices1 = model.getFaceIndices1(); @@ -2263,11 +2382,11 @@ else if (color3 == -1) } if (shouldCalculateFaceNormal) { - calculateFaceNormalInt( + calculateFaceNormal( faceNormals, - modelVertices[vertexOffsetA], modelVertices[vertexOffsetA + 1], modelVertices[vertexOffsetA + 2], - modelVertices[vertexOffsetB], modelVertices[vertexOffsetB + 1], modelVertices[vertexOffsetB + 2], - modelVertices[vertexOffsetC], modelVertices[vertexOffsetC + 1], modelVertices[vertexOffsetC + 2] + floatModelVertices[vertexOffsetA], floatModelVertices[vertexOffsetA + 1], floatModelVertices[vertexOffsetA + 2], + floatModelVertices[vertexOffsetB], floatModelVertices[vertexOffsetB + 1], floatModelVertices[vertexOffsetB + 2], + floatModelVertices[vertexOffsetC], floatModelVertices[vertexOffsetC + 1], floatModelVertices[vertexOffsetC + 2] ); } @@ -2303,29 +2422,28 @@ else if (color3 == -1) final VertexWriteCache vb = writeCache.getVertexBuffer(hasAlpha); final VertexWriteCache tb = writeCache.getTextureBuffer(hasAlpha); - final int texturedFaceIdx = tb.putFace( - color1, color2, color3, - materialData, materialData, materialData, - 0, 0, 0 - ); + final int modelIdx = hasAlpha ? alphaModelIdx : opaqueModelIdx; + final int texturedFaceIdx = tb.putModelFace(color1, color2, color3, materialData); - vb.putDynamicVertex( - modelVertices[vertexOffsetA], modelVertices[vertexOffsetA + 1], modelVertices[vertexOffsetA + 2], + vb.putVertex( + intModelVertices[vertexOffsetA], intModelVertices[vertexOffsetA + 1], intModelVertices[vertexOffsetA + 2], faceUVs[0], faceUVs[1], faceUVs[2], faceNormals[0], faceNormals[1], faceNormals[2], - texturedFaceIdx + texturedFaceIdx, false, modelIdx ); - vb.putDynamicVertex( - modelVertices[vertexOffsetB], modelVertices[vertexOffsetB + 1], modelVertices[vertexOffsetB + 2], + + vb.putVertex( + intModelVertices[vertexOffsetB], intModelVertices[vertexOffsetB + 1], intModelVertices[vertexOffsetB + 2], faceUVs[4], faceUVs[5], faceUVs[6], faceNormals[3], faceNormals[4], faceNormals[5], - texturedFaceIdx + texturedFaceIdx, false, modelIdx ); - vb.putDynamicVertex( - modelVertices[vertexOffsetC], modelVertices[vertexOffsetC + 1], modelVertices[vertexOffsetC + 2], + + vb.putVertex( + intModelVertices[vertexOffsetC], intModelVertices[vertexOffsetC + 1], intModelVertices[vertexOffsetC + 2], faceUVs[8], faceUVs[9], faceUVs[10], faceNormals[6], faceNormals[7], faceNormals[8], - texturedFaceIdx + texturedFaceIdx, false, modelIdx ); } @@ -2518,14 +2636,14 @@ private void putTerrainTriangle( ) { final var vb = writeCache.getVertexBuffer(); final var tb = writeCache.getTextureBuffer(); - int faceIdx = tb.putFace( + int faceIdx = tb.putStaticFace( colorA, colorB, colorC, packedMaterial, packedMaterial, packedMaterial, terrainData, terrainData, terrainData ); - vb.putStaticVertex(x0, y0, z0, u0, v0, 0, 0, -1, 0, faceIdx, false); - vb.putStaticVertex(x1, y1, z1, u1, v1, 0, 0, -1, 0, faceIdx, false); - vb.putStaticVertex(x2, y2, z2, u2, v2, 0, 0, -1, 0, faceIdx, false); + vb.putVertex(x0, y0, z0, u0, v0, 0, 0, -1, 0, faceIdx, false, 0); + vb.putVertex(x1, y1, z1, u1, v1, 0, 0, -1, 0, faceIdx, false, 0); + vb.putVertex(x2, y2, z2, u2, v2, 0, 0, -1, 0, faceIdx, false, 0); } public static void calculateFaceNormalInt( @@ -2534,7 +2652,8 @@ public static void calculateFaceNormalInt( int vx2, int vy2, int vz2, int vx3, int vy3, int vz3 ) { - calculateFaceNormal(out, + calculateFaceNormal( + out, Float.intBitsToFloat(vx1), Float.intBitsToFloat(vy1), Float.intBitsToFloat(vz1), Float.intBitsToFloat(vx2), Float.intBitsToFloat(vy2), Float.intBitsToFloat(vz2), Float.intBitsToFloat(vx3), Float.intBitsToFloat(vy3), Float.intBitsToFloat(vz3) diff --git a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java index 6975b03ed3..7fe148949c 100644 --- a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java +++ b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java @@ -49,7 +49,35 @@ private void flushAndGrow() { stagingBuffer = PooledArrayType.INT.ensureCapacity(stagingBuffer, min(stagingBuffer.length * 2, maxCapacity)); } - public int putFace( + public int findStaticFace( + int alphaBiasHslA, int alphaBiasHslB, int alphaBiasHslC, + int materialDataA, int materialDataB, int materialDataC, + int terrainDataA, int terrainDataB, int terrainDataC + ) { + final int[] stagingBuffer = this.stagingBuffer; + final int stagingPosition = this.stagingPosition; + + for (int i = 0; i < stagingPosition; i += 9) { + if (stagingBuffer[i] == alphaBiasHslA + && stagingBuffer[i + 1] == alphaBiasHslB + && stagingBuffer[i + 2] == alphaBiasHslC + + && stagingBuffer[i + 3] == materialDataA + && stagingBuffer[i + 4] == materialDataB + && stagingBuffer[i + 5] == materialDataC + + && stagingBuffer[i + 6] == terrainDataA + && stagingBuffer[i + 7] == terrainDataB + && stagingBuffer[i + 8] == terrainDataC) { + final int textureFaceIdx = outputBuffer.position() + i; + return textureFaceIdx; + } + } + + return -1; + } + + public int putStaticFace( int alphaBiasHslA, int alphaBiasHslB, int alphaBiasHslC, int materialDataA, int materialDataB, int materialDataC, int terrainDataA, int terrainDataB, int terrainDataC @@ -57,7 +85,7 @@ public int putFace( if (stagingPosition + 9 > stagingBuffer.length) flushAndGrow(); - final int textureFaceIdx = (outputBuffer.position() + stagingPosition) / 3; + final int textureFaceIdx = outputBuffer.position() + stagingPosition; final int[] stagingBuffer = this.stagingBuffer; final int stagingPosition = this.stagingPosition; @@ -78,35 +106,46 @@ public int putFace( return textureFaceIdx; } - public void putDynamicVertex( - int x, int y, int z, - float u, float v, float w, - int nx, int ny, int nz, - int textureFaceIdx - ) { - if (stagingPosition + 8 > stagingBuffer.length) + public int findModelFace(int alphaBiasHslA, int alphaBiasHslB, int alphaBiasHslC, int materialData) { + final int[] stagingBuffer = this.stagingBuffer; + final int stagingPosition = this.stagingPosition; + + for (int i = 0; i < stagingPosition; i += 4) { + if (stagingBuffer[i] == alphaBiasHslA + && stagingBuffer[i + 1] == alphaBiasHslB + && stagingBuffer[i + 2] == alphaBiasHslC + && stagingBuffer[i + 3] == materialData) { + final int textureFaceIdx = outputBuffer.position() + i; + return (1 << 30) | textureFaceIdx; + } + } + + return -1; + } + + public int putModelFace(int alphaBiasHslA, int alphaBiasHslB, int alphaBiasHslC, int materialData) { + if (stagingPosition + 4 > stagingBuffer.length) flushAndGrow(); + final int textureFaceIdx = outputBuffer.position() + stagingPosition; + final int[] stagingBuffer = this.stagingBuffer; final int stagingPosition = this.stagingPosition; - stagingBuffer[stagingPosition] = x; - stagingBuffer[stagingPosition + 1] = y; - stagingBuffer[stagingPosition + 2] = z; - stagingBuffer[stagingPosition + 3] = float16(v) << 16 | float16(u); - stagingBuffer[stagingPosition + 4] = float16(w); - stagingBuffer[stagingPosition + 5] = (ny & 0xFFFF) << 16 | nx & 0xFFFF; - stagingBuffer[stagingPosition + 6] = nz & 0xFFFF; - stagingBuffer[stagingPosition + 7] = textureFaceIdx; + stagingBuffer[stagingPosition] = alphaBiasHslA; + stagingBuffer[stagingPosition + 1] = alphaBiasHslB; + stagingBuffer[stagingPosition + 2] = alphaBiasHslC; + stagingBuffer[stagingPosition + 3] = materialData; - this.stagingPosition += 8; + this.stagingPosition += 4; + return (1 << 30) | textureFaceIdx; } - public void putStaticVertex( + public void putVertex( int x, int y, int z, float u, float v, float w, int nx, int ny, int nz, - int textureFaceIdx, boolean windingReversed + int textureFaceIdx, boolean windingReversed, int modelIdx ) { if (stagingPosition + 7 > stagingBuffer.length) flushAndGrow(); @@ -120,7 +159,7 @@ public void putStaticVertex( stagingBuffer[stagingPosition + 3] = float16(w); // Unnormalized normals, assumed to be within short max stagingBuffer[stagingPosition + 4] = (ny & 0xFFFF) << 16 | nx & 0xFFFF; - stagingBuffer[stagingPosition + 5] = nz & 0xFFFF; + stagingBuffer[stagingPosition + 5] = (modelIdx & 0xFFFF) << 16 | nz & 0xFFFF; stagingBuffer[stagingPosition + 6] = (windingReversed ? 1 << 31 : 0) | textureFaceIdx; this.stagingPosition += 7; diff --git a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java index 20a8ffd81d..46f09debbe 100644 --- a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java +++ b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java @@ -1,8 +1,8 @@ package rs117.hd.renderer.zone; import com.google.inject.Injector; -import java.nio.IntBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Set; @@ -10,38 +10,28 @@ import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; +import net.runelite.api.geometry.*; +import net.runelite.api.model.*; import net.runelite.client.callback.ClientThread; -import org.lwjgl.system.MemoryStack; import rs117.hd.HdPlugin; import rs117.hd.opengl.uniforms.UBOWorldViews; import rs117.hd.opengl.uniforms.UBOWorldViews.WorldViewStruct; +import rs117.hd.scene.DisplacementManager; import rs117.hd.utils.Camera; import rs117.hd.utils.CommandBuffer; import rs117.hd.utils.DestructibleHandler; -import rs117.hd.utils.buffer.GLBuffer; -import rs117.hd.utils.collections.ConcurrentPool; +import rs117.hd.utils.HDUtils; +import rs117.hd.utils.collections.PooledArrayType; import rs117.hd.utils.jobs.JobGroup; -import static org.lwjgl.opengl.GL33C.*; -import static rs117.hd.renderer.zone.DynamicModelVAO.METADATA_SIZE; +import static net.runelite.api.Constants.*; +import static rs117.hd.renderer.zone.FrameContext.VAO_COUNT; import static rs117.hd.renderer.zone.SceneManager.NUM_ZONES; -import static rs117.hd.renderer.zone.ZoneRenderer.FRAMES_IN_FLIGHT; +import static rs117.hd.utils.MathUtils.*; import static rs117.hd.utils.collections.Util.quickSort; @Slf4j public class WorldViewContext { - public static final int VAO_OPAQUE = 0; - public static final int VAO_ALPHA = 1; - public static final int VAO_PLAYER = 2; - public static final int VAO_SHADOW = 3; - public static final int VAO_PRESCENE = 4; - public static final int VAO_COUNT = 5; - - public static final ConcurrentPool DYNAMIC_MODEL_VAO_STAGING_POOL = - new ConcurrentPool<>(() -> new DynamicModelVAO("DynamicModelVAO::Staging", true)); - public static final ConcurrentPool DYNAMIC_MODEL_VAO_POOL = - new ConcurrentPool<>(() -> new DynamicModelVAO("DynamicModelVAO", false)); - @Inject private Injector injector; @@ -51,27 +41,36 @@ public class WorldViewContext { @Inject private HdPlugin plugin; + @Inject + private ZoneRenderer zoneRenderer; + @Inject private SceneManager sceneManager; + @Inject + private DisplacementManager displacementManager; + final int worldViewId; final int sizeX, sizeZ; @Nullable WorldViewStruct uboWorldViewStruct; - ZoneSceneContext sceneContext; + public ZoneSceneContext sceneContext; Zone[][] zones; - GLBuffer vboM; boolean isLoading = true; + boolean isBoat = false; + SimplePolygon boatDisplacementOctagon; + int minLevel, level, maxLevel; Set hideRoofIds; private final Comparator alphaSortComparator = Comparator.comparingInt((Zone z) -> z.dist).reversed(); private final List alphaZones = new ArrayList<>(); + private final int[] dynamicDrawRanges = new int[VAO_COUNT * 2]; + CommandBuffer vaoSceneCmd; CommandBuffer vaoDirectionalCmd; - final DynamicModelVAO[][] dynamicModelVaos = new DynamicModelVAO[FRAMES_IN_FLIGHT][VAO_COUNT]; public long loadTime; public long uploadTime; @@ -106,62 +105,6 @@ public void initialize(Injector injector) { zones[x][z] = injector.getInstance(Zone.class); } - void initBuffers() { - if (vboM != null) - return; - - vboM = new GLBuffer("WorldViewMetadata", GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW, 0); - vboM.initialize(METADATA_SIZE); - try (MemoryStack stack = MemoryStack.stackPush()) { - IntBuffer buf = stack.mallocInt(3); - buf.put(uboWorldViewStruct == null ? 0 : uboWorldViewStruct.worldViewIdx + 1); - buf.put(0).put(0); - buf.flip(); - vboM.upload(buf); - } - - long start = System.nanoTime(); - for (int i = 0; i < VAO_COUNT; i++) { - final boolean needsStaging = i == VAO_OPAQUE || i == VAO_PLAYER || i == VAO_SHADOW; - final var POOL = needsStaging ? DYNAMIC_MODEL_VAO_STAGING_POOL : DYNAMIC_MODEL_VAO_POOL; - for (int k = 0; k < FRAMES_IN_FLIGHT; k++) { - DynamicModelVAO dynamicModelVao = dynamicModelVaos[k][i] = POOL.acquire(); - if (dynamicModelVao.getVao() == 0) - dynamicModelVao.initialize(); - dynamicModelVao.bindMetadataVAO(vboM); - } - } - log.trace("WorldViewContext - WorldViewId: {} initBuffers took {}ms", worldViewId, (System.nanoTime() - start) / 1000000); - } - - void map() { - for (int i = 0; i < VAO_COUNT; i++) - dynamicModelVaos[plugin.frame % FRAMES_IN_FLIGHT][i].map(); - } - - DynamicModelVAO.View beginDraw(int type, int faces) { - return dynamicModelVaos[plugin.frame % FRAMES_IN_FLIGHT][type].beginDraw(faces); - } - - DynamicModelVAO.View beginDraw(int type, int playerDrawIndex, int faces) { - return dynamicModelVaos[plugin.frame % FRAMES_IN_FLIGHT][type].beginDraw(playerDrawIndex, faces); - } - - int obtainDrawIndex(int type) { - return dynamicModelVaos[plugin.frame % FRAMES_IN_FLIGHT][type].obtainDrawIndex(); - } - - void drawAll(int type, CommandBuffer cmd) { - dynamicModelVaos[plugin.frame % FRAMES_IN_FLIGHT][type].draw(cmd); - } - - void unmap() { - for (int i = 0; i < VAO_COUNT; i++) { - final boolean shouldCoalesce = i == VAO_OPAQUE || i == VAO_PLAYER || i == VAO_SHADOW; - dynamicModelVaos[plugin.frame % FRAMES_IN_FLIGHT][i].unmap(shouldCoalesce); - } - } - void sortStaticAlphaModels(Camera camera) { alphaZones.clear(); @@ -188,21 +131,41 @@ void sortStaticAlphaModels(Camera camera) { } } + void resetDrawRanges() { + for (int i = 0; i < VAO_COUNT; i++) + dynamicDrawRanges[i * 2] = dynamicDrawRanges[i * 2 + 1] = 0; + } + + synchronized void trackDrawRange(DynamicModelVAO.View view, int type) { + dynamicDrawRanges[type * 2] = min(dynamicDrawRanges[type * 2], view.getDrawIdx()); + dynamicDrawRanges[type * 2 + 1] = max(dynamicDrawRanges[type * 2 + 1], view.getDrawIdx() + 1); + } + + DynamicModelVAO.View beginDraw(int type, int faces) { + FrameContext ctx = zoneRenderer.frameContext(); + DynamicModelVAO.View view = ctx.dynamicModelVaos[type].beginDraw(faces); + trackDrawRange(view, type); + return view; + } + + DynamicModelVAO.View beginDraw(int type, int playerDrawIndex, int faces) { + FrameContext ctx = zoneRenderer.frameContext(); + DynamicModelVAO.View view = ctx.dynamicModelVaos[type].beginDraw(playerDrawIndex, faces); + trackDrawRange(view, type); + return view; + } + + void drawAll(int type, CommandBuffer cmd) { + FrameContext ctx = zoneRenderer.frameContext(); + ctx.dynamicModelVaos[type].draw(cmd, dynamicDrawRanges[type * 2], dynamicDrawRanges[type * 2 + 1]); + } + void handleZoneSwap(int zx, int zz, boolean queue) { Zone curZone = zones[zx][zz]; ZoneUploadJob uploadTask = curZone.uploadJob; if (uploadTask == null) return; - if (!uploadTask.isQueued()) { - if (queue && uploadTask.revealAfterTimestampMs < System.currentTimeMillis()) { - log.trace("queueing zone({}): [{}-{},{}]", uploadTask.zone.hashCode(), worldViewId, zx, zz); - uploadTask.revealAfterTimestampMs = 0; - uploadTask.queue(streamingGroup, sceneManager.getGenerateSceneDataTask()); - } - return; - } - if (uploadTask.isDone()) { curZone.uploadJob = null; if (uploadTask.ranToCompletion() && !uploadTask.wasCancelled()) { @@ -242,9 +205,18 @@ void handleZoneSwap(int zx, int zz, boolean queue) { } void processZoneSwaps() { - for (int x = 0; x < sizeX; x++) - for (int z = 0; z < sizeZ; z++) + for (int x = 0; x < sizeX; x++) { + for (int z = 0; z < sizeZ; z++) { handleZoneSwap(x, z, true); + + final Zone zone = zones[x][z]; + if (zone.fadingAlpha <= 0) + continue; + + zone.fadingAlpha = max(0.0f, zone.fadingAlpha - plugin.deltaTime); + zone.setMetadata(this, sceneContext, x, z); + } + } } void processZoneRebuilds() { @@ -281,25 +253,10 @@ void free() { uboWorldViewStruct.free(); uboWorldViewStruct = null; - for (int i = 0; i < VAO_COUNT; i++) { - for (int k = 0; k < FRAMES_IN_FLIGHT; k++) { - if (dynamicModelVaos[k][i] == null) - continue; - final var POOL = dynamicModelVaos[k][i].hasStagingBuffer() ? - DYNAMIC_MODEL_VAO_STAGING_POOL : - DYNAMIC_MODEL_VAO_POOL; - POOL.recycle(dynamicModelVaos[k][i]); - } - } - for (int x = 0; x < sizeX; ++x) for (int z = 0; z < sizeZ; ++z) zones[x][z].destroy(); - if (vboM != null) - vboM.destroy(); - vboM = null; - isLoading = true; } @@ -312,7 +269,6 @@ void invalidate() { void invalidateZone(int zx, int zz) { Zone curZone = zones[zx][zz]; - long revealAfterTimestampMs = 0; if (curZone.uploadJob != null) { Zone pendingZone = curZone.uploadJob.zone; log.trace( @@ -323,7 +279,6 @@ void invalidateZone(int zx, int zz) { zz, pendingZone.hashCode() ); - revealAfterTimestampMs = curZone.uploadJob.revealAfterTimestampMs; curZone.uploadJob.cancel(); curZone.uploadJob.release(); @@ -335,10 +290,87 @@ void invalidateZone(int zx, int zz) { newZone.dirty = zones[zx][zz].dirty; curZone.uploadJob = ZoneUploadJob.build(this, sceneContext, newZone, false, zx, zz); - curZone.uploadJob.revealAfterTimestampMs = revealAfterTimestampMs; + curZone.uploadJob.queue(invalidationGroup, sceneManager.getGenerateSceneDataTask()); + } - // Queue right away, so we can wait for it while in the POH in order to hide building mode placeholders - if (sceneContext.isInHouse || revealAfterTimestampMs <= 0) - curZone.uploadJob.queue(invalidationGroup, sceneManager.getGenerateSceneDataTask()); + void buildBoatDisplacement() { + Tile[][][] tiles = sceneContext.scene.getExtendedTiles(); + for (int z = 0; z < MAX_Z; z++) { + for (int x = 0; x < sceneContext.sizeX; x++) { + for (int y = 0; y < sceneContext.sizeZ; y++) { + final Tile tile = tiles[z][x][y]; + if (tile == null) + continue; + + GameObject[] gameObjects = tile.getGameObjects(); + for (int g = 0; g < gameObjects.length; g++) { + GameObject gameObject = gameObjects[g]; + if (gameObject == null) + continue; + + Renderable renderable = gameObject.getRenderable(); + if (renderable == null) + continue; + + if (displacementManager.boatIds.contains(gameObject.getId())) { + Model model = null; + if (renderable instanceof Model) { + model = (Model) renderable; + } else if (renderable instanceof DynamicObject) { + model = ((DynamicObject) renderable).getModelZbuf(); + } + + final int centerX = gameObject.getX(); + final int centerY = gameObject.getZ(); + final int centerZ = gameObject.getY(); + + if (model != null) { + // find all verticies that are under + int[] xs = PooledArrayType.INT.borrow(model.getVerticesCount()); + int[] ys = PooledArrayType.INT.borrow(model.getVerticesCount()); + int underwaterCount = 0; + try { + final float[] xVertices = model.getVerticesX(); + final float[] yVertices = model.getVerticesY(); + final float[] zVertices = model.getVerticesZ(); + + model.calculateBoundsCylinder(); + + final int vertexCount = model.getVerticesCount(); + final AABB modelAABB = model.getAABB(0); + + final int waterLine = modelAABB.getCenterY() + (int) (modelAABB.getExtremeY() * 0.5f); + + for (int i = 0; i < vertexCount; i++) { + final float posY = centerY + yVertices[i]; + if (posY > waterLine) { + xs[underwaterCount] = centerX + (int) xVertices[i]; + ys[underwaterCount] = centerZ + (int) zVertices[i]; + underwaterCount++; + } + } + + if (underwaterCount > 0) { + // Fill the rest of the array with the last vertex + Arrays.fill(xs, underwaterCount, xs.length, xs[underwaterCount - 1]); + Arrays.fill(ys, underwaterCount, ys.length, ys[underwaterCount - 1]); + + // Build Convex Hull around all verticies which are below the water plane + // Then Reduce the convex hull down to just 8 points + boatDisplacementOctagon = Jarvis.convexHull(xs, ys); + boatDisplacementOctagon = HDUtils.reducePolygon(boatDisplacementOctagon, 8); + isBoat = true; + } + } finally { + PooledArrayType.INT.release(xs); + PooledArrayType.INT.release(ys); + } + return; + } + } + } + } + } + } } } diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index f4ff6f361f..211ee5a4ab 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -34,6 +34,7 @@ import static rs117.hd.HdPlugin.GL_CAPS; import static rs117.hd.HdPlugin.SUPPORTS_INDIRECT_DRAW; import static rs117.hd.HdPlugin.checkGLErrors; +import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_MODEL_DATA; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; import static rs117.hd.renderer.zone.ZoneRenderer.eboAlpha; import static rs117.hd.utils.MathUtils.*; @@ -49,19 +50,29 @@ public class Zone implements Destructible { // Zone vertex format // pos short vec3(x, y, z) // uvw short vec3(u, v, w) - // normal short vec3(nx, ny, nz) + // normal/modelIdx short vec4(nx, ny, nz, modelIdx) // texturedFaceIdx int public static final int VERT_SIZE = 28; + public static final int VERT_SIZE_INTS = VERT_SIZE / Integer.BYTES; // alphaBiasHsl ivec3 // materialData ivec3 // terrainData ivec3 public static final int TEXTURE_SIZE = 36; + public static final int TEXTURE_INTS = TEXTURE_SIZE / Integer.BYTES; + + // position vec3 + // height float + // fade float + public static final int MODEL_DATA_SIZE = 28; + public static final int MODEL_DATA_INTS = MODEL_DATA_SIZE / Integer.BYTES; // Metadata format // worldViewIndex int int // sceneOffset int vec2(x, y) - public static final int METADATA_SIZE = 12; + // fade float float + public static final int METADATA_SIZE = 16; + public static final int METADATA_INTS = METADATA_SIZE / Integer.BYTES; public static int LEVEL_COUNT = MAX_Z; public static final int LEVEL_WATER_SURFACE = LEVEL_COUNT++; @@ -74,10 +85,10 @@ public class Zone implements Destructible { public int glVaoA; public int bufLenA; - public int sizeO, sizeA, sizeF; + public int sizeO, sizeA, sizeF, sizeM; @Nullable public GLBuffer vboO, vboA, vboM; - public GLTextureBuffer tboF; + public GLTextureBuffer tboF, tboM; public boolean initialized; // whether the zone vao and vbos are ready public boolean cull; // whether the zone is queued for deletion @@ -95,6 +106,7 @@ public class Zone implements Destructible { final StaticAlphaSortingJob alphaSortingJob = new StaticAlphaSortingJob(); ZoneUploadJob uploadJob; + float fadingAlpha; int[] levelOffsets = new int[LEVEL_COUNT]; // buffer pos in ints for the end of the level @@ -105,7 +117,7 @@ public class Zone implements Destructible { final List alphaModels = new ArrayList<>(0); final ConcurrentLinkedQueue pendingModelJobs = new ConcurrentLinkedQueue<>(); - public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f) { + public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f, GLTextureBuffer m) { assert glVao == 0; assert glVaoA == 0; if (o == null && a == null || f == null) @@ -127,6 +139,7 @@ public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f) { } tboF = f; + tboM = m; } public static void freeZones(@Nullable Zone[][] zones) { @@ -168,6 +181,11 @@ public void destroy() { tboF = null; } + if (tboM != null) { + tboM.destroy(); + tboM = null; + } + if (glVao != 0) { glDeleteVertexArrays(glVao); glVao = 0; @@ -227,6 +245,8 @@ public void unmap() { vboA.unmap(); if (tboF != null) tboF.unmap(); + if (tboM != null) + tboM.unmap(); if (vboO != null) { this.bufLen = vboO.mapped().byteView().position() / VERT_SIZE; @@ -269,6 +289,11 @@ private void setupVao(int vao, int buffer, int metadata) { glVertexAttribDivisor(7, 1); glVertexAttribIPointer(7, 2, GL_INT, METADATA_SIZE, 4); + // Scene offset + glEnableVertexAttribArray(8); + glVertexAttribDivisor(8, 1); + glVertexAttribPointer(8, 1, GL_FLOAT, false, METADATA_SIZE, 12); + checkGLErrors(); glBindVertexArray(0); @@ -279,14 +304,16 @@ public void setMetadata(WorldViewContext viewContext, SceneContext sceneContext, if (vboM == null) return; + float fade = saturate(fadingAlpha); int baseX = (mx - (sceneContext.sceneOffset >> 3)) << 10; int baseZ = (mz - (sceneContext.sceneOffset >> 3)) << 10; try (MemoryStack stack = MemoryStack.stackPush()) { - IntBuffer buf = stack.mallocInt(3) + IntBuffer buf = stack.mallocInt(METADATA_INTS) .put(viewContext.uboWorldViewStruct != null ? viewContext.uboWorldViewStruct.worldViewIdx + 1 : 0) .put(baseX) - .put(baseZ); + .put(baseZ) + .put(Float.floatToIntBits(fade)); buf.flip(); vboM.upload(buf); } @@ -327,6 +354,8 @@ private void convertForDraw(int vertSize) { } void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, boolean roofShadows) { + if (fadingAlpha > 1.0) + return; drawIdx = 0; int currentLevel = ctx.level; @@ -380,10 +409,14 @@ void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, boolean roofShadows) lastDrawMode = STATIC_UNSORTED; lastVao = glVao; lastTboF = tboF.getTexId(); + lastTboM = tboM != null ? tboM.getTexId() : 0; flush(cmd); } void renderOpaqueLevel(CommandBuffer cmd, int level) { + if (fadingAlpha > 1.0) + return; + drawIdx = 0; pushRange(this.levelOffsets[level - 1], this.levelOffsets[level]); @@ -394,6 +427,7 @@ void renderOpaqueLevel(CommandBuffer cmd, int level) { lastDrawMode = STATIC_UNSORTED; lastVao = glVao; lastTboF = tboF.getTexId(); + lastTboM = tboM != null ? tboM.getTexId() : 0; flush(cmd); } @@ -419,6 +453,7 @@ public static final class AlphaModel { short rid; int vao; int tboF; + int tboM; byte level; byte lx, lz, ux, uz; // lower/upper zone coords byte zofx, zofz; // for temp alpha models, offset of source zone from target zone @@ -459,7 +494,8 @@ int calculateDepth(int cx, int cy, int cz, int zx, int zz) { void setView(DynamicModelVAO.View view) { vao = view.vao; - tboF = view.tboTexId; + tboF = view.tboFId; + tboM = view.tboMId; startpos = view.getStartOffset(); endpos = view.getEndOffset(); } @@ -470,6 +506,7 @@ void addAlphaModel( MaterialManager materialManager, int vao, int tboF, + int tboM, Model model, ModelOverride modelOverride, int startpos, @@ -495,6 +532,7 @@ void addAlphaModel( m.z = (short) z; m.vao = vao; m.tboF = tboF; + m.tboM = tboM; m.rid = (short) rid; m.level = (byte) level; if (lx > -1) { @@ -663,7 +701,7 @@ synchronized AlphaModel requestTempAlphaModel(ModelOverride modelOverride, int l m.y = (short) y; m.z = (short) z; m.level = (byte) level; - m.vao = m.tboF = m.rid = m.lx = m.lz = m.ux = m.uz = -1; + m.vao = m.tboF = m.tboM = m.rid = m.lx = m.lz = m.ux = m.uz = -1; m.flags = 0; m.zofx = m.zofz = 0; alphaModels.add(m); @@ -701,6 +739,7 @@ synchronized void postAlphaPass() { private static int lastDrawMode; private static int lastVao; private static int lastTboF; + private static int lastTboM; private static int lastzx, lastzz; static class AlphaModelComparator implements Comparator { @@ -755,7 +794,7 @@ void renderAlpha( boolean depthOnly, boolean includeRoof ) { - if (alphaModels.isEmpty()) + if (alphaModels.isEmpty() || fadingAlpha > 1.0) return; int minLevel = ctx.minLevel; @@ -793,6 +832,7 @@ void renderAlpha( if (lastDrawMode != drawMode || lastVao != m.vao || lastTboF != m.tboF || + lastTboM != m.tboM || lastzx != (zx - m.zofx) || lastzz != (zz - m.zofz) ) { @@ -800,6 +840,7 @@ void renderAlpha( lastDrawMode = drawMode; lastVao = m.vao; lastTboF = m.tboF; + lastTboM = m.tboM; lastzx = zx - m.zofx; lastzz = zz - m.zofz; } @@ -841,6 +882,7 @@ private void flush(CommandBuffer cmd) { long byteOffset = 4L * (eboAlphaOffset - vertexCount); cmd.BindVertexArray(lastVao, eboAlpha); cmd.BindTextureUnit(GL_TEXTURE_BUFFER, lastTboF, TEXTURE_UNIT_TEXTURED_FACES); + cmd.BindTextureUnit(GL_TEXTURE_BUFFER, lastTboM, TEXTURE_UNIT_MODEL_DATA); // The EBO & IDO is bound by in ZoneRenderer if (GL_CAPS.OpenGL40 && SUPPORTS_INDIRECT_DRAW) { cmd.DrawElementsIndirect(GL_TRIANGLES, vertexCount, (int) (byteOffset / 4L), ZoneRenderer.indirectDrawCmdsStaging); @@ -850,9 +892,10 @@ private void flush(CommandBuffer cmd) { } alphaFaceCount = 0; } else if (drawIdx != 0) { - convertForDraw(lastDrawMode == STATIC_UNSORTED ? VERT_SIZE : DynamicModelVAO.VERT_SIZE); + convertForDraw(VERT_SIZE); cmd.BindVertexArray(lastVao); cmd.BindTextureUnit(GL_TEXTURE_BUFFER, lastTboF, TEXTURE_UNIT_TEXTURED_FACES); + cmd.BindTextureUnit(GL_TEXTURE_BUFFER, lastTboM, TEXTURE_UNIT_MODEL_DATA); if (drawIdx == 1) { if (GL_CAPS.OpenGL40 && SUPPORTS_INDIRECT_DRAW) { cmd.DrawArraysIndirect(GL_TRIANGLES, drawOff[0], drawEnd[0], ZoneRenderer.indirectDrawCmdsStaging); @@ -922,6 +965,7 @@ synchronized void multizoneLocs(SceneContext ctx, int zx, int zz, Camera camera, m2.z = m.z; m2.vao = m.vao; m2.tboF = m.tboF; + m2.tboM = m.tboM; m2.rid = m.rid; m2.level = m.level; m2.lx = m.lx; diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 39c6412acf..73a19d4e94 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -47,11 +47,13 @@ import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; import rs117.hd.opengl.shader.ShadowShaderProgram; +import rs117.hd.opengl.uniforms.UBODisplacement; import rs117.hd.opengl.uniforms.UBOLights; import rs117.hd.opengl.uniforms.UBOWorldViews; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; import rs117.hd.renderer.Renderer; +import rs117.hd.scene.DisplacementManager; import rs117.hd.scene.EnvironmentManager; import rs117.hd.scene.LightManager; import rs117.hd.scene.ProceduralGenerator; @@ -63,6 +65,7 @@ import rs117.hd.utils.CommandBuffer; import rs117.hd.utils.HDUtils; import rs117.hd.utils.Mat4; +import rs117.hd.utils.NpcDisplacementCache; import rs117.hd.utils.RenderState; import rs117.hd.utils.ShadowCasterVolume; import rs117.hd.utils.buffer.GLBuffer; @@ -81,10 +84,10 @@ import static rs117.hd.HdPlugin.ORTHOGRAPHIC_ZOOM; import static rs117.hd.HdPlugin.checkGLErrors; import static rs117.hd.HdPluginConfig.*; -import static rs117.hd.renderer.zone.WorldViewContext.VAO_OPAQUE; -import static rs117.hd.renderer.zone.WorldViewContext.VAO_PLAYER; -import static rs117.hd.renderer.zone.WorldViewContext.VAO_PRESCENE; -import static rs117.hd.renderer.zone.WorldViewContext.VAO_SHADOW; +import static rs117.hd.renderer.zone.FrameContext.VAO_OPAQUE; +import static rs117.hd.renderer.zone.FrameContext.VAO_PLAYER; +import static rs117.hd.renderer.zone.FrameContext.VAO_PRESCENE; +import static rs117.hd.renderer.zone.FrameContext.VAO_SHADOW; import static rs117.hd.utils.MathUtils.*; @Slf4j @@ -94,9 +97,11 @@ public class ZoneRenderer implements Renderer { private static int TEXTURE_UNIT_COUNT = HdPlugin.TEXTURE_UNIT_COUNT; public static final int TEXTURE_UNIT_TEXTURED_FACES = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; + public static final int TEXTURE_UNIT_MODEL_DATA = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; private static int UNIFORM_BLOCK_COUNT = HdPlugin.UNIFORM_BLOCK_COUNT; public static final int UNIFORM_BLOCK_WORLD_VIEWS = UNIFORM_BLOCK_COUNT++; + public static final int UNIFORM_BLOCK_DISPLACEMENT = UNIFORM_BLOCK_COUNT++; @Inject private Injector injector; @@ -128,6 +133,12 @@ public class ZoneRenderer implements Renderer { @Inject private ModelStreamingManager modelStreamingManager; + @Inject + private DisplacementManager displacementManager; + + @Inject + private NpcDisplacementCache npcDisplacementCache; + @Inject private FrameTimer frameTimer; @@ -146,6 +157,11 @@ public class ZoneRenderer implements Renderer { @Inject private UBOWorldViews uboWorldViews; + @Inject + private UBODisplacement uboDisplacement; + + public final FrameContext[] frameContexts = new FrameContext[FRAMES_IN_FLIGHT]; + public final Camera sceneCamera = new Camera().setReverseZ(true); public final Camera directionalCamera = new Camera().setOrthographic(true); public final ShadowCasterVolume directionalShadowCasterVolume = new ShadowCasterVolume(directionalCamera); @@ -167,6 +183,10 @@ public class ZoneRenderer implements Renderer { private boolean shouldClearShadowFbo; private boolean shouldDrawRoofShadows; + public FrameContext frameContext() { + return frameContexts[plugin.frame % FRAMES_IN_FLIGHT]; + } + @Override public boolean supportsGpu(GLCapabilities glCaps) { return glCaps.OpenGL33; @@ -196,6 +216,7 @@ public void initialize() { jobSystem.startUp(config.cpuUsageLimit()); uboWorldViews.initialize(UNIFORM_BLOCK_WORLD_VIEWS); + uboDisplacement.initialize(UNIFORM_BLOCK_DISPLACEMENT); sceneManager.initialize(uboWorldViews); modelStreamingManager.initialize(); @@ -212,6 +233,7 @@ public void destroy() { modelStreamingManager.destroy(); sceneManager.destroy(); uboWorldViews.destroy(); + uboDisplacement.destroy(); if (SceneUploader.POOL != null) SceneUploader.POOL.destroy(); @@ -231,7 +253,8 @@ public void addShaderIncludes(ShaderIncludes includes) { includes .define("MAX_SIMULTANEOUS_WORLD_VIEWS", UBOWorldViews.MAX_SIMULTANEOUS_WORLD_VIEWS) .addInclude("WORLD_VIEW_GETTER", () -> plugin.generateGetter("WorldView", UBOWorldViews.MAX_SIMULTANEOUS_WORLD_VIEWS)) - .addUniformBuffer(uboWorldViews); + .addUniformBuffer(uboWorldViews) + .addUniformBuffer(uboDisplacement); } @Override @@ -255,9 +278,20 @@ private void initializeBuffers() { indirectDrawCmds = new GLBuffer("indirectDrawCmds", GL_DRAW_INDIRECT_BUFFER, GL_STREAM_DRAW).initialize(MiB); indirectDrawCmdsStaging = new GpuIntBuffer(); + + for (int i = 0; i < FRAMES_IN_FLIGHT; i++) { + frameContexts[i] = new FrameContext(); + frameContexts[i].initBuffers(); + } } private void destroyBuffers() { + for (int i = 0; i < FRAMES_IN_FLIGHT; i++) { + if (frameContexts[i] != null) + frameContexts[i].destroy(); + frameContexts[i] = null; + } + if (eboAlpha != null) eboAlpha.destroy(); eboAlpha = null; @@ -306,12 +340,19 @@ public void preSceneDraw( ctx.hideRoofIds = hideRoofIds; ctx.vaoSceneCmd.reset(); ctx.vaoDirectionalCmd.reset(); + ctx.resetDrawRanges(); - if (ctx.uboWorldViewStruct != null) + if (ctx.uboWorldViewStruct != null) { ctx.uboWorldViewStruct.update(); - if (scene.getWorldViewId() == WorldView.TOPLEVEL) + if (ctx.isBoat) + displacementManager.addBoat(ctx.uboWorldViewStruct, ctx.boatDisplacementOctagon); + } + + if (scene.getWorldViewId() == WorldView.TOPLEVEL) { + frameContext().map(); preSceneDrawTopLevel(scene, cameraX, cameraY, cameraZ, cameraPitch, cameraYaw); + } ctx.completeInvalidation(); @@ -322,8 +363,6 @@ public void preSceneDraw( ctx.sortStaticAlphaModels(sceneCamera); - ctx.map(); - if (scene.getWorldViewId() == WorldView.TOPLEVEL) { Model skybox = scene.getSkybox(); if (skybox != null) { @@ -338,9 +377,10 @@ public void preSceneDraw( null, null, true, - VAO_PRESCENE, + VAO_OPAQUE, -1, 0, + 0, cameraX, cameraY, cameraZ ); } @@ -361,8 +401,6 @@ private void preSceneDrawTopLevel( Scene scene, float cameraX, float cameraY, float cameraZ, float cameraPitch, float cameraYaw ) { - jobSystem.processPendingClientCallbacks(); - scene.setDrawDistance(plugin.getDrawDistance()); // Ensure that the previous frames commands have finished flushing @@ -637,6 +675,14 @@ private void preSceneDrawTopLevel( plugin.uboGlobal.underwaterCausticsStrength.set(environmentManager.currentUnderwaterCausticsStrength); plugin.uboGlobal.elapsedTime.set((float) (plugin.elapsedTime % MAX_FLOAT_WITH_128TH_PRECISION)); + displacementManager.addLocalPlayer(); + + uboDisplacement.windDirectionX.set(cos(environmentManager.currentWindAngle)); + uboDisplacement.windDirectionZ.set(sin(environmentManager.currentWindAngle)); + uboDisplacement.windStrength.set(environmentManager.currentWindStrength); + uboDisplacement.windCeiling.set(environmentManager.currentWindCeiling); + uboDisplacement.windOffset.set(plugin.windOffset); + if (plugin.configColorFilter != ColorFilter.NONE) { plugin.uboGlobal.colorFilter.set(plugin.configColorFilter.ordinal()); plugin.uboGlobal.colorFilterPrevious.set(plugin.configColorFilterPrevious.ordinal()); @@ -665,8 +711,6 @@ public void postSceneDraw(Scene scene) { return; try { - jobSystem.processPendingClientCallbacks(); - WorldViewContext ctx = sceneManager.getContext(scene); if (ctx == null || !sceneManager.isRoot(ctx) && ctx.isLoading) return; @@ -704,6 +748,8 @@ private void postDrawTopLevel() { frameTimer.begin(Timer.RENDER_FRAME); shouldRenderScene = true; + uboDisplacement.upload(); + // TODO: Add proper support for stat tracking to the FrameTimer or elsewhere plugin.drawnDynamicRenderableCount += modelStreamingManager.getDrawnDynamicRenderableCount(); @@ -1035,14 +1081,6 @@ public void drawPass(Projection projection, Scene scene, int pass) { case DrawCallbacks.PASS_ALPHA: modelStreamingManager.ensureAsyncUploadsComplete(null); - if (sceneManager.isRoot(ctx)) - frameTimer.begin(Timer.UNMAP_ROOT_CTX); - - ctx.unmap(); - - if (sceneManager.isRoot(ctx)) - frameTimer.end(Timer.UNMAP_ROOT_CTX); - // Draw opaque ctx.drawAll(VAO_OPAQUE, ctx.vaoSceneCmd); ctx.drawAll(VAO_OPAQUE, ctx.vaoDirectionalCmd); @@ -1061,6 +1099,12 @@ public void drawPass(Projection projection, Scene scene, int pass) { ctx.drawAll(VAO_PLAYER, ctx.vaoSceneCmd); ctx.vaoSceneCmd.ColorMask(true, true, true, true); + if (sceneManager.isRoot(ctx)) { + frameTimer.begin(Timer.FRAME_CONTEXT_UNMAP); + frameContext().unmap(); + frameTimer.end(Timer.FRAME_CONTEXT_UNMAP); + } + for (int zx = 0; zx < ctx.sizeX; ++zx) for (int zz = 0; zz < ctx.sizeZ; ++zz) ctx.zones[zx][zz].postAlphaPass(); @@ -1094,6 +1138,8 @@ public void drawDynamic( final long start = System.nanoTime(); try { modelStreamingManager.drawTemp(renderThreadId, projection, scene, tileObject, r, m, orient, x, y, z); + + displacementManager.addCharacterPosition(scene, x, z, r, m); } catch (Exception ex) { log.error("Error in drawDynamic:", ex); } finally { @@ -1109,6 +1155,8 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj frameTimer.begin(Timer.DRAW_TEMP); try { modelStreamingManager.drawTemp(-1, worldProjection, scene, gameObject, gameObject.getRenderable(), m, orientation, x, y, z); + + displacementManager.addCharacterPosition(scene, x, z, gameObject.getRenderable(), m); } catch (Exception ex) { log.error("Error in drawTemp:", ex); } finally { @@ -1190,8 +1238,6 @@ public void draw(int overlayColor) { plugin.drawUi(overlayColor); frameTimer.end(Timer.DRAW_SUBMIT); - jobSystem.processPendingClientCallbacks(); - frameTimer.end(Timer.DRAW_FRAME); frameTimer.end(Timer.RENDER_FRAME); @@ -1287,6 +1333,7 @@ public void despawnWorldView(WorldView worldView) { public void swapScene(Scene scene) { try { sceneManager.swapScene(scene); + npcDisplacementCache.clear(); } catch (Throwable ex) { log.error("Error during swapScene:", ex); plugin.stopPlugin(); diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java b/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java index ab3374979d..1a603237c7 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java @@ -20,7 +20,6 @@ public final class ZoneUploadJob extends Job { Zone zone; int x, z; - long revealAfterTimestampMs; boolean shouldUnmap; @Override @@ -72,12 +71,20 @@ private void mapZoneVertexBuffers() { GLTextureBuffer f = null; sz = zone.sizeF * Zone.TEXTURE_SIZE; if (sz > 0) { - f = new GLTextureBuffer("Zone::TBO", GL_STATIC_DRAW); + f = new GLTextureBuffer("Zone::TexturedFaces", GL_STATIC_DRAW); f.initialize(sz); f.map(MAP_WRITE); } - zone.initialize(o, a, f); + GLTextureBuffer m = null; + sz = zone.sizeM * Zone.MODEL_DATA_SIZE; + if (sz > 0) { + m = new GLTextureBuffer("Zone::ModelData", GL_STATIC_DRAW); + m.initialize(sz); + m.map(MAP_WRITE); + } + + zone.initialize(o, a, f, m); zone.setMetadata(viewContext, sceneContext, x, z); } catch (Throwable ex) { log.warn( @@ -108,7 +115,6 @@ protected void onReleased() { sceneContext = null; zone.uploadJob = null; zone = null; - revealAfterTimestampMs = 0; POOL.recycle(this); } diff --git a/src/main/java/rs117/hd/scene/DisplacementManager.java b/src/main/java/rs117/hd/scene/DisplacementManager.java new file mode 100644 index 0000000000..2e7e5c82f8 --- /dev/null +++ b/src/main/java/rs117/hd/scene/DisplacementManager.java @@ -0,0 +1,225 @@ +package rs117.hd.scene; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.*; +import net.runelite.api.geometry.*; +import rs117.hd.HdPlugin; +import rs117.hd.opengl.uniforms.UBODisplacement; +import rs117.hd.opengl.uniforms.UBOWorldViews.WorldViewStruct; +import rs117.hd.opengl.uniforms.UniformBuffer.Property; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; +import rs117.hd.utils.NpcDisplacementCache; +import rs117.hd.utils.collections.IntHashSet; + +import static net.runelite.api.Constants.*; +import static net.runelite.api.Perspective.*; +import static rs117.hd.utils.HDUtils.EXTENDED_SCENE_OFFSET; +import static rs117.hd.utils.MathUtils.*; + +@Singleton +@Slf4j +public class DisplacementManager { + public static final int MAX_CHARACTER_POSITION_COUNT = 50; + public static final int MAX_BOAT_COUNT = 20; + + private static final Comparator CHARACTER_POSITION_PAIR_COMPARATOR = + Comparator.comparingDouble(p -> p.dist); + + @Inject + private Client client; + + @Inject + private HdPlugin plugin; + + @Inject + private FrameTimer frameTimer; + + @Inject + private GamevalManager gamevalManager; + + @Inject + private NpcDisplacementCache npcDisplacementCache; + + private final int[] packedContour = new int[4]; + private final float[] projected = new float[4]; + + private final WorldViewStruct[] boatWorldViews = new WorldViewStruct[MAX_BOAT_COUNT]; + private final SimplePolygon[] boatDisplacementPolygons = new SimplePolygon[MAX_BOAT_COUNT]; + private final boolean[] groundItems = new boolean[EXTENDED_SCENE_SIZE * EXTENDED_SCENE_SIZE]; + private int writtenBoats; + + public final IntHashSet boatIds = new IntHashSet(); + + private final ArrayList characterPositionsPairs = new ArrayList<>(MAX_CHARACTER_POSITION_COUNT); + private int writtenCharacterPositions; + private float playerPosX, playerPosZ; + + public void initialize() { + try (var handle = gamevalManager.obtainHandle()) { + for (var entry : handle.getObjects().entrySet()) { + if (entry.getKey().contains("SAILING_BOAT_HULL_")) + boatIds.add(entry.getValue()); + } + } + } + + private CharacterPositionPair getCharacterPositionPair() { + if (writtenCharacterPositions >= characterPositionsPairs.size()) { + CharacterPositionPair newPair = new CharacterPositionPair(); + characterPositionsPairs.add(newPair); + return newPair; + } + + return characterPositionsPairs.get(writtenCharacterPositions); + } + + public void addBoat(WorldViewStruct worldViewStruct, SimplePolygon displacedPolygon) { + if (writtenBoats >= MAX_BOAT_COUNT) + return; + + boatWorldViews[writtenBoats] = worldViewStruct; + boatDisplacementPolygons[writtenBoats] = displacedPolygon; + writtenBoats++; + } + + public void addLocalPlayer() { + if (!plugin.configCharacterDisplacement) + return; + + var localPlayer = client.getLocalPlayer(); + if (localPlayer == null) + return; + + // The local player needs to be added first for distance culling + var lp = localPlayer.getLocalLocation(); + if (lp != null) + addCharacterPosition(lp.getX(), lp.getY(), (int) (LOCAL_TILE_SIZE * 1.33f), 1.0f); + } + + public void addCharacterPosition(Scene scene, int x, int z, Renderable renderable, Model m) { + if (!plugin.configCharacterDisplacement) + return; + + if (scene.getWorldViewId() != WorldView.TOPLEVEL) + return; + + if (renderable instanceof NPC) { + var npc = (NPC) renderable; + var entry = npcDisplacementCache.get(npc); + if (entry.canDisplace) { + int displacementRadius = entry.idleRadius; + if (displacementRadius == -1) { + displacementRadius = m.getXYZMag(); // Fallback to model radius since we don't know the idle radius yet + if (npc.getIdlePoseAnimation() == npc.getPoseAnimation() && npc.getAnimation() == -1) { + displacementRadius *= 2; // Double the idle radius, so that it fits most other animations + entry.idleRadius = displacementRadius; + } + } + addCharacterPosition(x, z, displacementRadius, 1.0f); + } + } else if (renderable instanceof Player && renderable != client.getLocalPlayer()) { + addCharacterPosition(x, z, (int) (LOCAL_TILE_SIZE * 1.33f), 1.0f); + } else if (renderable instanceof TileItem) { + int tileExX = clamp(EXTENDED_SCENE_OFFSET + (x / 128), 0, EXTENDED_SCENE_SIZE - 1); + int tileExY = clamp(EXTENDED_SCENE_OFFSET + (z / 128), 0, EXTENDED_SCENE_SIZE - 1); + final int tileIdx = tileExX * EXTENDED_SCENE_SIZE + tileExY; + if (!groundItems[tileIdx]) { + groundItems[tileIdx] = true; + addCharacterPosition(x, z, (int) (LOCAL_TILE_SIZE * 0.5f), 4.0f); + } + } + } + + public void addCharacterPosition(int localX, int localZ, int modelRadius, float strength) { + if (!plugin.configCharacterDisplacement) + return; + + if (plugin.enableDetailedTimers) + frameTimer.begin(Timer.CHARACTER_DISPLACEMENT); + + int writeIndex = writtenCharacterPositions; + CharacterPositionPair pair = getCharacterPositionPair(); + characterPositionsPairs.remove(writeIndex); + + pair.x = localX; + pair.z = localZ; + pair.radius = modelRadius * 1.25f; + pair.strength = strength; + + if (writeIndex == 0) { + playerPosX = pair.x; + playerPosZ = pair.z; + pair.dist = 0.0f; + } else { + pair.dist = abs(playerPosX - pair.x) + abs(playerPosZ - pair.z); + + if (writeIndex > 1) { + int index = Collections.binarySearch( + characterPositionsPairs.subList(1, writeIndex), + pair, + CHARACTER_POSITION_PAIR_COMPARATOR + ); + + writeIndex = index >= 0 ? index : -index - 1; + } + } + + characterPositionsPairs.add(writeIndex, pair); + writtenCharacterPositions++; + + if (plugin.enableDetailedTimers) + frameTimer.end(Timer.CHARACTER_DISPLACEMENT); + } + + public void writeBoatData(UBODisplacement.BoatStruct[] boatContours, Property boatCount) { + final int count = min(writtenBoats, MAX_BOAT_COUNT); + boatCount.set(count); + writtenBoats = 0; + + for (int i = 0; i < count; i++) { + final WorldViewStruct worldViewStruct = boatWorldViews[i]; + final SimplePolygon polygon = boatDisplacementPolygons[i]; + final UBODisplacement.BoatStruct boatStruct = boatContours[i]; + + for (int j = 0; j < polygon.size(); j++) { + worldViewStruct.project(vec4(projected, polygon.getX(j), 0, polygon.getY(j), 1.0f)); + + packedContour[j % 4] = float16(projected[0]) | (float16(projected[2]) << 16); + + if ((j + 1) % 4 == 0) + boatStruct.contour[j / 4].set(packedContour); + } + } + } + + public void writeCharacterPositions(Property[] characterPositions, Property characterPositionCount) { + final int count = min(writtenCharacterPositions, characterPositions.length); + characterPositionCount.set(count); + writtenCharacterPositions = 0; + + for (int i = 0; i < count; i++) { + CharacterPositionPair pair = characterPositionsPairs.get(i); + pair.dist = Float.MAX_VALUE; + + if (i < characterPositions.length) + characterPositions[i].set(pair.x, pair.z, pair.radius, pair.strength); + } + + Arrays.fill(groundItems, false); + } + + private static class CharacterPositionPair { + public float x; + public float z; + public float radius; + public float strength; + public float dist = Float.MAX_VALUE; + } +} diff --git a/src/main/java/rs117/hd/scene/SceneContext.java b/src/main/java/rs117/hd/scene/SceneContext.java index 7281b5e253..d45b7efb39 100644 --- a/src/main/java/rs117/hd/scene/SceneContext.java +++ b/src/main/java/rs117/hd/scene/SceneContext.java @@ -19,8 +19,8 @@ import rs117.hd.utils.collections.Int2ObjectHashMap; import static net.runelite.api.Constants.*; -import static net.runelite.api.Constants.SCENE_SIZE; import static net.runelite.api.Perspective.*; +import static rs117.hd.utils.HDUtils.EXTENDED_SCENE_OFFSET; import static rs117.hd.utils.MathUtils.*; public class SceneContext { @@ -81,7 +81,7 @@ public SceneContext(Client client, Scene scene, int expandedMapLoadingChunks) { this.scene = scene; this.expandedMapLoadingChunks = scene.isInstance() ? 0 : expandedMapLoadingChunks; sizeX = sizeZ = EXTENDED_SCENE_SIZE; - sceneOffset = (EXTENDED_SCENE_SIZE - SCENE_SIZE) / 2; + sceneOffset = EXTENDED_SCENE_OFFSET; sceneBase = findSceneBase(); sceneBounds = findSceneBounds(sceneBase); } diff --git a/src/main/java/rs117/hd/scene/model_overrides/ModelOverride.java b/src/main/java/rs117/hd/scene/model_overrides/ModelOverride.java index a464c3bcc8..76ea6f7978 100644 --- a/src/main/java/rs117/hd/scene/model_overrides/ModelOverride.java +++ b/src/main/java/rs117/hd/scene/model_overrides/ModelOverride.java @@ -85,6 +85,7 @@ public class ModelOverride public WindDisplacement windDisplacementMode = WindDisplacement.DISABLED; public int windDisplacementModifier = 0; public boolean invertDisplacementStrength = false; + public int positionTileSnapping = -1; public int depthBias = -1; public boolean disablePrioritySorting = false; @@ -319,6 +320,7 @@ public ModelOverride copy() { windDisplacementMode, windDisplacementModifier, invertDisplacementStrength, + positionTileSnapping, depthBias, disablePrioritySorting, setHue, diff --git a/src/main/java/rs117/hd/utils/HDUtils.java b/src/main/java/rs117/hd/utils/HDUtils.java index d5f01f0e52..180200ba88 100644 --- a/src/main/java/rs117/hd/utils/HDUtils.java +++ b/src/main/java/rs117/hd/utils/HDUtils.java @@ -32,16 +32,19 @@ import java.io.BufferedReader; import java.io.FileReader; import java.lang.management.ManagementFactory; +import java.util.Arrays; import javax.annotation.Nullable; import javax.inject.Singleton; import javax.swing.JFrame; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; +import net.runelite.api.geometry.*; import net.runelite.client.util.OSType; import rs117.hd.data.ObjectType; import rs117.hd.scene.areas.AABB; import rs117.hd.scene.areas.Area; import rs117.hd.scene.water_types.WaterType; +import rs117.hd.utils.collections.PooledArrayType; import static net.runelite.api.Constants.*; import static net.runelite.api.Constants.SCENE_SIZE; @@ -581,4 +584,41 @@ public static void drawStringCentered(Graphics2D g, String s) { var b = g.getClipBounds(); drawStringCentered(g, s, b.width / 2.f, b.height / 2.f); } + + public static SimplePolygon reducePolygon(SimplePolygon polygon, int maxPoints) { + int size = polygon.size(); + final int[] x = PooledArrayType.INT.borrow(size); + final int[] y = PooledArrayType.INT.borrow(size); + + try { + polygon.copyTo(x, y, 0); + + while (size > maxPoints) { + int worst = 0; + long worstArea = Long.MAX_VALUE; + for (int i = 0; i < size; i++) { + int prev = (i - 1 + size) % size; + int next = (i + 1) % size; + + // Twice the area of the triangle that would be shaved off by dropping vertex i + int area = abs(area2D(x[prev], y[prev], x[i], y[i], x[next], y[next])); + if (area < worstArea) { + worstArea = area; + worst = i; + } + } + + for (int i = worst; i < size - 1; i++) { + x[i] = x[i + 1]; + y[i] = y[i + 1]; + } + size--; + } + + return new SimplePolygon(Arrays.copyOf(x, size), Arrays.copyOf(y, size), size); + } finally { + PooledArrayType.INT.release(x); + PooledArrayType.INT.release(y); + } + } } diff --git a/src/main/java/rs117/hd/utils/MathUtils.java b/src/main/java/rs117/hd/utils/MathUtils.java index edb24da8b9..1fa9b55111 100644 --- a/src/main/java/rs117/hd/utils/MathUtils.java +++ b/src/main/java/rs117/hd/utils/MathUtils.java @@ -471,6 +471,14 @@ public static float product(float... v) { return product; } + public static int area2D(int aX, int aY, int bX, int bY, int cX, int cY) { + return (aX - cX) * (bY - cY) - (bX - cX) * (aY - cY); + } + + public static int area2D(int[] a, int[] b, int[] c) { + return area2D(a[0], a[1], b[0], b[1], c[0], c[1]); + } + /** * Yields incorrect results if either of the input vectors is used as the output vector. */ @@ -938,6 +946,41 @@ public static int float16(float value) { return sign | exponent << 10 | mantissa >> 13; } + public static float unpackFloat16(int val) { + int h = val & 0xFFFF; + + int sign = (h & 0x8000) << 16; // move sign into bit 31 + int exponent = (h >>> 10) & 0x1F; // 5 bits + int mantissa = h & 0x3FF; // 10 bits + + int f; // resulting float32 bit pattern + + if (exponent == 0) { + if (mantissa == 0) { + f = sign; // signed zero + } else { + // Subnormal half -> normalize into a normal float32 + exponent = 1; + while ((mantissa & 0x400) == 0) { + mantissa <<= 1; + exponent -= 1; + } + mantissa &= 0x3FF; // drop the now-implicit leading bit + int exp32 = exponent - 15 + 127; + f = sign | (exp32 << 23) | (mantissa << 13); + } + } else if (exponent == 0x1F) { + // Inf / NaN + f = sign | 0x7F800000 | (mantissa << 13); + } else { + // Normalized value + int exp32 = exponent - 15 + 127; + f = sign | (exp32 << 23) | (mantissa << 13); + } + + return Float.intBitsToFloat(f); + } + public static String formatBytes(long bytes) { if (bytes < 0) return "-" + formatBytes(bytes == Long.MIN_VALUE ? Long.MAX_VALUE : -bytes); diff --git a/src/main/java/rs117/hd/utils/NpcDisplacementCache.java b/src/main/java/rs117/hd/utils/NpcDisplacementCache.java index 71f56b39bf..7a9bf03b91 100644 --- a/src/main/java/rs117/hd/utils/NpcDisplacementCache.java +++ b/src/main/java/rs117/hd/utils/NpcDisplacementCache.java @@ -1,13 +1,12 @@ package rs117.hd.utils; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; import net.runelite.api.*; import rs117.hd.scene.GamevalManager; +import rs117.hd.utils.collections.Int2ObjectHashMap; +import rs117.hd.utils.collections.IntHashSet; @Singleton public class NpcDisplacementCache { @@ -34,22 +33,22 @@ public Entry reset() { } } - private final HashMap cache = new HashMap<>(MAX_SIZE); - private Set ANIM_ID_IGNORE_LIST = Collections.emptySet(); + private final Int2ObjectHashMap cache = new Int2ObjectHashMap<>(MAX_SIZE); + private final IntHashSet ANIM_ID_IGNORE_LIST = new IntHashSet(); public void initialize() { try (var gamevals = gamevalManager.obtainHandle()) { - HashSet idsToIgnore = new HashSet<>(); for (var substringToIgnore : ANIM_IGNORE_LIST) for (var entry : gamevals.getAnims().entrySet()) if (entry.getKey().contains(substringToIgnore)) - idsToIgnore.add(entry.getValue()); - ANIM_ID_IGNORE_LIST = Set.copyOf(idsToIgnore); + ANIM_ID_IGNORE_LIST.add(entry.getValue()); + ANIM_ID_IGNORE_LIST.trimToSize(); } } public void destroy() { - ANIM_ID_IGNORE_LIST = Collections.emptySet(); + ANIM_ID_IGNORE_LIST.clear(); + ANIM_ID_IGNORE_LIST.trimToSize(); cache.clear(); } @@ -57,11 +56,11 @@ public int size() { return cache.size(); } - public void clear() { + public synchronized void clear() { cache.clear(); } - public Entry get(NPC npc) { + public synchronized Entry get(NPC npc) { int npcId = npc.getId(); var entry = cache.get(npcId); @@ -69,13 +68,21 @@ public Entry get(NPC npc) { if (cache.size() >= NpcDisplacementCache.MAX_SIZE) { long oldestMs = Long.MAX_VALUE; int oldestNpcId = -1; - for (var e : cache.entrySet()) - if (e.getValue().lastAccessMs < oldestMs) + for (var e : cache) { + if (e.getValue().lastAccessMs < oldestMs) { oldestNpcId = e.getKey(); - entry = cache.remove(oldestNpcId).reset(); - } else { - entry = new NpcDisplacementCache.Entry(); + entry = e.getValue(); + } + } + + if (entry != null) { + cache.remove(oldestNpcId); + entry.reset(); + } } + + if (entry == null) + entry = new NpcDisplacementCache.Entry(); cache.put(npcId, entry); // Check if NPC is allowed to displace diff --git a/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java b/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java index 1b58183741..38ef7464d3 100644 --- a/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java @@ -3,52 +3,60 @@ import lombok.Getter; import static org.lwjgl.opengl.GL33C.*; +import static rs117.hd.HdPlugin.GL_CAPS; public class GLTextureBuffer extends GLBuffer { + + public static boolean isRGBASupported() { + return GL_CAPS.GL_ARB_texture_buffer_object; + } + @Getter private int texId; + private final int internalFormat; + public GLTextureBuffer(String name, int usage) { this(name, usage, 0); } public GLTextureBuffer(String name, int usage, int storageFlags) { super(name, GL_TEXTURE_BUFFER, usage, storageFlags); + + internalFormat = isRGBASupported() ? GL_RGBA32I : GL_RGB32I; } @Override public GLTextureBuffer initialize(long initialCapacity) { super.initialize(initialCapacity); - // Create texture texId = glGenTextures(); glBindTexture(target, texId); - - // RGB32 signed integer texture buffer - glTexBuffer(target, GL_RGB32I, id); - + glTexBuffer(target, internalFormat, id); glBindTexture(target, 0); + return this; } @Override public boolean ensureCapacity(long byteOffset, long numBytes) { int oldId = id; - boolean resized = super.ensureCapacity(byteOffset, numBytes); + final boolean resized = super.ensureCapacity(byteOffset, numBytes); + if (oldId != id) { glBindTexture(target, texId); - glTexBuffer(target, GL_RGB32I, id); + glTexBuffer(target, internalFormat, id); glBindTexture(target, 0); } + return resized; } @Override public void destroy() { - if (texId != 0) { + if (texId != 0) glDeleteTextures(texId); - texId = 0; - } + texId = 0; super.destroy(); } diff --git a/src/main/java/rs117/hd/utils/jobs/JobHandle.java b/src/main/java/rs117/hd/utils/jobs/JobHandle.java index d56c29640d..4eed413e88 100644 --- a/src/main/java/rs117/hd/utils/jobs/JobHandle.java +++ b/src/main/java/rs117/hd/utils/jobs/JobHandle.java @@ -241,6 +241,24 @@ void cancel(boolean block) throws InterruptedException { boolean isCancelled() { return jobState.get() == STATE_CANCELLED; } boolean isCompleted() { return jobState.get() == STATE_COMPLETED; } + private boolean attemptProcessItem() throws InterruptedException { + if (worker != null || !isInQueue() || depCount.get() != 0 || !item.canStart() || !setRunning(null)) + return false; + + try { + item.onRun(); + item.ranToCompletion.set(true); + } catch (Throwable ex) { + log.warn("Encountered an error whilst processing: {}", hashCode(), ex); + item.encounteredError.set(true); + cancel(false); + } finally { + setCompleted(); + } + + return true; + } + boolean await() throws InterruptedException { return await(-1); } @@ -254,8 +272,11 @@ boolean await(int timeoutNanos) throws InterruptedException { if (isClientThread) { long start = System.currentTimeMillis(); int seconds = 0; - while (!tryAcquireSharedNanos(0, TimeUnit.MILLISECONDS.toNanos(1))) { + do { JOB_SYSTEM.processPendingClientCallbacks(); + if (attemptProcessItem()) + return true; + Thread.yield(); long elapsed = System.currentTimeMillis() - start; int newSeconds = (int) (elapsed / 1000); @@ -279,8 +300,11 @@ boolean await(int timeoutNanos) throws InterruptedException { return false; } } - } + } while (!tryAcquireSharedNanos(0, TimeUnit.MILLISECONDS.toNanos(1))); } else { + if (attemptProcessItem()) + return true; + if (timeoutNanos > 0) { if (!tryAcquireSharedNanos(0, timeoutNanos)) return false; diff --git a/src/main/resources/rs117/hd/comp.glsl b/src/main/resources/rs117/hd/comp.glsl index 56b6fe2c54..9f17b05101 100644 --- a/src/main/resources/rs117/hd/comp.glsl +++ b/src/main/resources/rs117/hd/comp.glsl @@ -45,6 +45,8 @@ layout(local_size_x = THREAD_COUNT) in; #include #include +#include + void main() { uint groupId = gl_WorkGroupID.x; uint localId = gl_LocalInvocationID.x * FACES_PER_THREAD; @@ -61,21 +63,10 @@ void main() { } } - ObjectWindSample windSample; - #if WIND_DISPLACEMENT - { - float modelNoise = noise((vec2(minfo.x, minfo.z) + vec2(windOffset)) * WIND_DISPLACEMENT_NOISE_RESOLUTION); - float angle = modelNoise * (PI / 2.0); - float c = cos(angle); - float s = sin(angle); - float y = minfo.y >> 16; - float height = minfo.y & 0xffff; - - windSample.direction = normalize(vec3(windDirectionX * c + windDirectionZ * s, 0.0, -windDirectionX * s + windDirectionZ * c)); - windSample.heightBasedStrength = saturate((abs(y) + height) / windCeiling) * windStrength; - windSample.displacement = windSample.direction.xyz * (windSample.heightBasedStrength * modelNoise); - } - #endif + ObjectWindSample windSample = computeWindSample( + vec3(minfo.x, float(minfo.y >> 16), minfo.z), + minfo.y & 0xffff + ); // Ensure all invocations have their shared variables initialized barrier(); diff --git a/src/main/resources/rs117/hd/comp_common.glsl b/src/main/resources/rs117/hd/comp_common.glsl index c047342af1..2e7ed438e7 100644 --- a/src/main/resources/rs117/hd/comp_common.glsl +++ b/src/main/resources/rs117/hd/comp_common.glsl @@ -26,14 +26,6 @@ #define UNIT PI / 1024.0f -// Needs to match Ordinal Values of WindDisplacement.Java -#define WIND_DISPLACEMENT_DISABLED 0 -#define WIND_DISPLACEMENT_OBJECT 1 -#define WIND_DISPLACEMENT_OBJECT_NO_GROUND_DISPLACEMENT 2 -#define WIND_DISPLACEMENT_VERTEX 3 -#define WIND_DISPLACEMENT_VERTEX_WITH_HEMISPHERE_BLEND 4 -#define WIND_DISPLACEMENT_VERTEX_JIGGLE 5 - #define HILLSKEW_NONE 0 #define HILLSKEW_MODEL 1 #define HILLSKEW_TILE_SNAPPING 2 @@ -50,12 +42,6 @@ struct ModelInfo { int z; // scene position z }; -struct ObjectWindSample { - vec3 direction; - vec3 displacement; - float heightBasedStrength; -}; - struct VertexData { vec3 pos; int ahsl; diff --git a/src/main/resources/rs117/hd/opengl/compute/cl_types.cl b/src/main/resources/rs117/hd/opengl/compute/cl_types.cl index 709b0c933d..e3f4789005 100644 --- a/src/main/resources/rs117/hd/opengl/compute/cl_types.cl +++ b/src/main/resources/rs117/hd/opengl/compute/cl_types.cl @@ -43,7 +43,7 @@ struct UBOCompute { float windOffset; int characterPositionCount; - float3 characterPositions[MAX_CHARACTER_POSITION_COUNT]; + float4 characterPositions[MAX_CHARACTER_POSITION_COUNT]; }; struct shared_data { diff --git a/src/main/resources/rs117/hd/opengl/compute/priority_render.cl b/src/main/resources/rs117/hd/opengl/compute/priority_render.cl index 715e6ac6c7..cdce1ab6d9 100644 --- a/src/main/resources/rs117/hd/opengl/compute/priority_render.cl +++ b/src/main/resources/rs117/hd/opengl/compute/priority_render.cl @@ -58,7 +58,7 @@ int tile_height(read_only image3d_t tileHeightMap, int z, int x, int y); void hillskew_vertex(read_only image3d_t tileHeightMap, float4 *v, int hillskew, int modelPosY, float modelHeight, int plane); void undoVanillaShading(struct VertexData *vertex, float3 unrotatedNormal); float3 applyCharacterDisplacement( - float3 characterPos, + float4 characterPos, float2 vertPos, float height, float strength, @@ -357,7 +357,7 @@ void undoVanillaShading(struct VertexData *vertex, float3 unrotatedNormal) { } float3 applyCharacterDisplacement( - float3 characterPos, + float4 characterPos, float2 vertPos, float height, float strength, @@ -370,7 +370,7 @@ float3 applyCharacterDisplacement( return (float3)(0.0f, 0.0f, 0.0f); float offsetFrac = clamp(1.0f - (offsetLen / fabs(characterPos.z)), 0.0f, 1.0f); - float displacementFrac = offsetFrac * offsetFrac; + float displacementFrac = offsetFrac * offsetFrac * characterPos.w; float3 horizontalDisplacement = normalize((float3)(offset.x, 0.0f, offset.y)) * (height * strength * displacementFrac * 0.5f); float3 verticalDisplacement = (float3)(0.0f, height * strength * displacementFrac, 0.0f); diff --git a/src/main/resources/rs117/hd/priority_render.glsl b/src/main/resources/rs117/hd/priority_render.glsl index bdfdf53ddf..e9b9955d6c 100644 --- a/src/main/resources/rs117/hd/priority_render.glsl +++ b/src/main/resources/rs117/hd/priority_render.glsl @@ -26,6 +26,7 @@ #include #include +#include uniform isampler3D tileHeightMap; @@ -204,132 +205,6 @@ void hillskew_vertex(inout vec3 v, int hillskewMode, float modelPosY, float mode } } -vec3 applyCharacterDisplacement(vec3 characterPos, vec2 vertPos, float height, float strength, inout float offsetAccum) { - vec2 offset = vertPos - characterPos.xy; - float offsetLen = length(offset); - - if (offsetLen >= characterPos.z) - return vec3(0); - - float offsetFrac = saturate(1.0 - (offsetLen / characterPos.z)); - float displacementFrac = offsetFrac * offsetFrac; - - vec3 horizontalDisplacement = normalize(vec3(offset.x, 0.0, offset.y)) * (height * strength * displacementFrac * 0.5); - vec3 verticalDisplacement = vec3(0.0, height * strength * displacementFrac, 0.0); - - offsetAccum += offsetFrac; - - return mix(horizontalDisplacement, verticalDisplacement, offsetFrac); -} - -float getModelWindDisplacementMod(int vertexFlags) { - const float modifiers[7] = { 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0}; - int modifierIDx = (vertexFlags >> MATERIAL_FLAG_WIND_MODIFIER) & 0x7; - return modifiers[modifierIDx]; -} - -void applyWindDisplacement(const ObjectWindSample windSample, int vertexFlags, float modelHeight, vec3 worldPos, - in vec3 vertA, in vec3 vertB, in vec3 vertC, - in vec3 normA, in vec3 normB, in vec3 normC, - inout vec3 displacementA, inout vec3 displacementB, inout vec3 displacementC -) { - int windDisplacementMode = (vertexFlags >> MATERIAL_FLAG_WIND_SWAYING) & 0x7; - if (windDisplacementMode <= WIND_DISPLACEMENT_DISABLED) - return; - - float strengthA = saturate(abs(vertA.y) / modelHeight); - float strengthB = saturate(abs(vertB.y) / modelHeight); - float strengthC = saturate(abs(vertC.y) / modelHeight); - - if ((vertexFlags >> MATERIAL_FLAG_INVERT_DISPLACEMENT_STRENGTH & 1) == 1) { - strengthA = 1.0 - strengthA; - strengthB = 1.0 - strengthB; - strengthC = 1.0 - strengthC; - } - - float modelDisplacementMod = getModelWindDisplacementMod(vertexFlags); -#if WIND_DISPLACEMENT - if (windDisplacementMode >= WIND_DISPLACEMENT_VERTEX) { - const float VertexSnapping = 150.0; // Snap so vertices which are almost overlapping will obtain the same noise value - const float VertexDisplacementMod = 0.2; // Avoid over stretching which can cause issues in ComputeUVs - float windNoiseA = mix(-0.5, 0.5, noise((snap(vertA, VertexSnapping).xz + vec2(windOffset)) * WIND_DISPLACEMENT_NOISE_RESOLUTION)); - float windNoiseB = mix(-0.5, 0.5, noise((snap(vertB, VertexSnapping).xz + vec2(windOffset)) * WIND_DISPLACEMENT_NOISE_RESOLUTION)); - float windNoiseC = mix(-0.5, 0.5, noise((snap(vertC, VertexSnapping).xz + vec2(windOffset)) * WIND_DISPLACEMENT_NOISE_RESOLUTION)); - - if (windDisplacementMode == WIND_DISPLACEMENT_VERTEX_WITH_HEMISPHERE_BLEND) { - const float minDist = 50; - const float blendDist = 10.0; - - float distBlendA = saturate(((abs(vertA.x) + abs(vertA.z)) - minDist) / blendDist); - float distBlendB = saturate(((abs(vertB.x) + abs(vertB.z)) - minDist) / blendDist); - float distBlendC = saturate(((abs(vertC.x) + abs(vertC.z)) - minDist) / blendDist); - - float heightFadeA = saturate((strengthA - 0.5) / 0.2); - float heightFadeB = saturate((strengthB - 0.5) / 0.2); - float heightFadeC = saturate((strengthC - 0.5) / 0.2); - - strengthA *= mix(0.0, mix(distBlendA, 1.0, heightFadeA), step(0.3, strengthA)); - strengthB *= mix(0.0, mix(distBlendB, 1.0, heightFadeB), step(0.3, strengthB)); - strengthC *= mix(0.0, mix(distBlendC, 1.0, heightFadeC), step(0.3, strengthC)); - } else { - if (windDisplacementMode == WIND_DISPLACEMENT_VERTEX_JIGGLE) { - vec3 vertASkew = safe_normalize(cross(normA.xyz, vec3(0, 1, 0))); - vec3 vertBSkew = safe_normalize(cross(normB.xyz, vec3(0, 1, 0))); - vec3 vertCSkew = safe_normalize(cross(normC.xyz, vec3(0, 1, 0))); - - strengthA *= modelDisplacementMod; - strengthB *= modelDisplacementMod; - strengthC *= modelDisplacementMod; - - displacementA = ((windNoiseA * (windSample.heightBasedStrength * strengthA) * 0.5) * vertASkew); - displacementB = ((windNoiseB * (windSample.heightBasedStrength * strengthB) * 0.5) * vertBSkew); - displacementC = ((windNoiseC * (windSample.heightBasedStrength * strengthC) * 0.5) * vertCSkew); - - vertASkew = safe_normalize(cross(normA.xyz, vec3(1, 0, 0))); - vertBSkew = safe_normalize(cross(normB.xyz, vec3(1, 0, 0))); - vertCSkew = safe_normalize(cross(normC.xyz, vec3(1, 0, 0))); - - displacementA += (((1.0 - windNoiseA) * (windSample.heightBasedStrength * strengthA) * 0.5) * vertASkew); - displacementB += (((1.0 - windNoiseB) * (windSample.heightBasedStrength * strengthB) * 0.5) * vertBSkew); - displacementC += (((1.0 - windNoiseC) * (windSample.heightBasedStrength * strengthC) * 0.5) * vertCSkew); - } else { - displacementA = ((windNoiseA * (windSample.heightBasedStrength * strengthA * VertexDisplacementMod)) * windSample.direction); - displacementB = ((windNoiseB * (windSample.heightBasedStrength * strengthB * VertexDisplacementMod)) * windSample.direction); - displacementC = ((windNoiseC * (windSample.heightBasedStrength * strengthC * VertexDisplacementMod)) * windSample.direction); - - strengthA = saturate(strengthA - VertexDisplacementMod) * modelDisplacementMod; - strengthB = saturate(strengthB - VertexDisplacementMod) * modelDisplacementMod; - strengthC = saturate(strengthC - VertexDisplacementMod) * modelDisplacementMod; - } - } - } - - if (windDisplacementMode != WIND_DISPLACEMENT_VERTEX_JIGGLE) { - // Object Displacement - displacementA += windSample.displacement * strengthA * modelDisplacementMod; - displacementB += windSample.displacement * strengthB * modelDisplacementMod; - displacementC += windSample.displacement * strengthC * modelDisplacementMod; - } -#endif - -#if CHARACTER_DISPLACEMENT - if (windDisplacementMode == WIND_DISPLACEMENT_OBJECT) { - vec2 worldVertA = (worldPos + vertA).xz; - vec2 worldVertB = (worldPos + vertB).xz; - vec2 worldVertC = (worldPos + vertC).xz; - - float fractAccum = 0.0; - for (int i = 0; i < characterPositionCount; i++) { - displacementA += applyCharacterDisplacement(characterPositions[i], worldVertA, modelHeight, strengthA, fractAccum); - displacementB += applyCharacterDisplacement(characterPositions[i], worldVertB, modelHeight, strengthB, fractAccum); - displacementC += applyCharacterDisplacement(characterPositions[i], worldVertC, modelHeight, strengthC, fractAccum); - if (fractAccum >= 2.0) - break; - } - } -#endif -} - void sort_and_insert(uint localId, const ModelInfo minfo, int thisPriority, int thisDistance, ObjectWindSample windSample) { int offset = minfo.offset; int size = minfo.size; @@ -370,7 +245,7 @@ void sort_and_insert(uint localId, const ModelInfo minfo, int thisPriority, int vec4 normB = normal[offset + localId * 3 + 1]; vec4 normC = normal[offset + localId * 3 + 2]; - applyWindDisplacement(windSample, vertexFlags, height, modelPos, + applyWindDisplacementFace(windSample, vertexFlags, height, modelPos, thisrvA.pos, thisrvB.pos, thisrvC.pos, normA.xyz, normB.xyz, normC.xyz, displacementA, displacementB, displacementC); diff --git a/src/main/resources/rs117/hd/scene/areas.json b/src/main/resources/rs117/hd/scene/areas.json index ece497adce..6234638820 100644 --- a/src/main/resources/rs117/hd/scene/areas.json +++ b/src/main/resources/rs117/hd/scene/areas.json @@ -490,6 +490,20 @@ [ 3200, 3470, 3200, 3474 ] ] }, + { + "name": "VARROCK_CASTLE_OUTDOOR_BANNERS", + "aabbs": [ + [ 3202, 3462, 3202, 3464 ], + [ 3223, 3461 ], + [ 3208, 3470 ] + ] + }, + { + "name": "VARROCK_CASTLE_OUTDOOR_DUMMIES", + "aabbs": [ + [ 3209, 3489, 3217, 3492 ] + ] + }, { "name": "VARROCK_CASTLE_DIRT_PATH_FIXES_1", "aabbs": [ @@ -4708,6 +4722,12 @@ [ 2375, 3458, 2375, 3458 ] ] }, + { + "name": "TREE_GNOME_STRONGHOLD_BANNERS_NO_WIND", + "aabbs": [ + [ 2465, 3498, 2467, 3498 ] + ] + }, { "name": "EYES_OF_GLOUPHRIE_GNOME_AND_GOBLIN_BATTLE_CUTSCENE", "aabbs": [ diff --git a/src/main/resources/rs117/hd/scene/model_overrides.json b/src/main/resources/rs117/hd/scene/model_overrides.json index 3e90bb5f35..3d41ef769d 100644 --- a/src/main/resources/rs117/hd/scene/model_overrides.json +++ b/src/main/resources/rs117/hd/scene/model_overrides.json @@ -1084,6 +1084,8 @@ { "description": "Kourend - Hosidius - Apple Tree", "baseMaterial": "BARK", + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -1, "objectIds": [ "KORE2_ORCHARD_APPLETREE" ], @@ -1092,9 +1094,13 @@ "uvOrientation": 89, "materialOverrides": { "LEAVES_SIDE": { + "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -1, "textureMaterial": "LEAVES_YELLOW_SIDE" }, "LEAVES_TOP": { + "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -1, "textureMaterial": "LEAVES_YELLOW_TOP" } }, @@ -1103,7 +1109,9 @@ "description": "Apple", "colors": "h == 16", "baseMaterial": "GRUNGE_3_DARK", - "uvType": "BOX" + "uvType": "BOX", + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -1 } ] }, @@ -1343,8 +1351,8 @@ { "description": "Spirit trees - Small", "baseMaterial": "BARK", - "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", - "windDisplacementModifier": -3, + "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -1, "textureMaterial": "LEAVES_SIDE_ALT", "objectIds": [ "SPIRIT_TREE_SEEDLING", @@ -1366,7 +1374,16 @@ ], "uvType": "BOX", "uvScale": 0.8, - "retainVanillaUvs": true + "retainVanillaUvs": true, + "colorOverrides": [ + { + "colors": "h == 5", + "baseMaterial": "BARK", + "uvType": "BOX", + "uvScale": 0.8, + "retainVanillaUvs": true + } + ] }, { "description": "Spirit trees - Big", @@ -1438,6 +1455,8 @@ "baseMaterial": "BARK", "doubleSidedFaces": true, "textureMaterial": "LEAVES_TOP_ALT", + "windDisplacementMode": "VERTEX", + "positionTileSnapping": 25, "objectIds": [ "TKCANOPYMIDDLE", "TKCANOPYMIDDLE2" @@ -1451,6 +1470,8 @@ "baseMaterial": "BARK", "doubleSidedFaces": true, "textureMaterial": "LEAVES_SIDE_ALT", + "windDisplacementMode": "VERTEX", + "positionTileSnapping": 25, "objectIds": [ "TKCANOPYCORNER2", "TKCANOPYFILLER2", @@ -1629,6 +1650,7 @@ "areas": [ "WARM_REGIONS" ], "baseMaterial": "BARK", "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -1, "objectIds": [ "YEW_TREE_SEEDLING", "YEW_TREE_1", @@ -1704,6 +1726,7 @@ "description": "Objects - Tree - Yew", "baseMaterial": "BARK", "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -1, "objectIds": [ "YEW_TREE_SEEDLING", "YEW_TREE_1", @@ -1933,6 +1956,7 @@ "description": "Objects - Tree - Teak - Depth Fix", "baseMaterial": "BARK", "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -1, "uvType": "BOX", "uvScale": 0.66, "depthBias": 1, @@ -1941,6 +1965,7 @@ "description": "Leaves", "colors": "h > 12", "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -1, "baseMaterial": "LEAF_VEINS", "uvScale": 0.2, "uvOrientation": 42, @@ -2031,6 +2056,7 @@ "description": "Jatoba Tree", "baseMaterial": "LIGHT_BARK", "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -1, "objectIds": [ "JATOBA_TREE" ], @@ -2042,7 +2068,8 @@ "baseMaterial": "GRUNGE_1_DARK", "uvType": "BOX", "uvScale": 0.3, - "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND" + "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -1 } ] }, @@ -2073,6 +2100,7 @@ "description": "Campohr Tree", "baseMaterial": "LIGHT_BARK", "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -1, "objectIds": [ "CAMPHOR_TREE" ], @@ -2732,6 +2760,7 @@ "POH_PLANTBSMALL2B", "DARKM_BRAMBLE_BUSH_2", "FLOWERS_LILLIES_RED01", + "FLOWERS_LILLIES_RED02", "LUNAR_LITTLE_FLOWERS_1", "LUNAR_LITTLE_FLOWERS_2", "LUNAR_LITTLE_FLOWERS_3" @@ -2756,11 +2785,12 @@ "description": "Wheat", "baseMaterial": "GRAY_90", "windDisplacementMode": "OBJECT", - "windDisplacementModifier": 1, + "windDisplacementModifier": 3, "objectIds": [ "FAI_VARROCK_WHEAT_CORNER", "FAI_VARROCK_WHEAT_SMALL", - "FAI_VARROCK_WHEAT" + "FAI_VARROCK_WHEAT", + "WHEAT" ] }, { @@ -2821,7 +2851,6 @@ "baseMaterial": "GRAY_90", "windDisplacementMode": "VERTEX", "objectIds": [ - "SUNFLOWER", "FAIRY2_BULLRUSH_FALLOFF", "FAIRY2_BULLRUSH_SMALL", "FAIRY2_BULLRUSH_TILE", @@ -2831,6 +2860,7 @@ "FAI_VARROCK_ROSES_EDGE_SHAPE_8", "FAI_VARROCK_ROSES_EDGE_B_SHAPE_8", "FAI_VARROCK_ROSES_EDGE_B", + "KOUREND_ROSES_WHITE", "AVIUM_CORN01", "AVIUM_CORN02", "WATERLILY1", @@ -3190,9 +3220,6 @@ "description": "Sailing objects that shouldn't cast any shadows", "castShadows": false, "objectIds": [ - "OCEAN_OUTCROP_SEAWEED01", - "OCEAN_OUTCROP_SEAWEED02", - "OCEAN_OUTCROP_SEAWEED03", "SAILING_HAZARD_FETID_WATERS_AMBIENT_3X3_FAST", "SAILING_HAZARD_FETID_WATERS_AMBIENT_3X3_SLOW", "SAILING_HAZARD_FETID_WATERS_AMBIENT_1X1", @@ -3280,6 +3307,22 @@ "XBOWS_INV_FLOOR" ] }, + { + "description": "Sailing - Kelp and Seaweed - Displacement", + "baseMaterial": "PLANT_GRUNGE_1", + "castShadows": false, + "windDisplacementMode": "OBJECT", + "objectIds": [ + "OCEAN_OUTCROP_SEAWEED01", + "OCEAN_OUTCROP_SEAWEED02", + "OCEAN_OUTCROP_SEAWEED03", + "OCEAN_PLANT_KELP01", + "OCEAN_PLANT_KELP02", + "OCEAN_PLANT_KELP03", + "OCEAN_PLANT_KELP04", + "OCEAN_PLANT_KELP05" + ] + }, { "description": "Sailing Wind Event that shouldn't cast any shadows and should have upwards normals", "castShadows": false, @@ -15485,6 +15528,60 @@ "uvType": "BOX", "uvOrientation": 512, "uvScale": 0.6 + }, + { + "description": "Training Dummy - Varrock - Indoors No Wind", + "baseMaterial": "BURLAP", + "objectIds": [ + "FAI_VARROCK_SWORD_DUMMY" + ], + "uvType": "BOX", + "uvScale": 0.35, + "colorOverrides": [ + { + "description": "Frame", + "colors": "h == 5 && s == 7 || h == 7", + "baseMaterial": "WOOD_GRAIN_3", + "shiftLightness": -2, + "uvType": "BOX", + "uvScale": 0.65 + }, + { + "description": "Rope", + "colors": "h == 0", + "baseMaterial": "METALLIC_1", + "shiftLightness": 10 + } + ] + }, + { + "description": "Training Dummy - Varrock - Outdoors with Wind", + "areas": [ "VARROCK_CASTLE_OUTDOOR_DUMMIES" ], + "baseMaterial": "BURLAP", + "windDisplacementMode": "OBJECT", + "invertDisplacementStrength": true, + "windDisplacementModifier": -3, + "objectIds": [ + "FAI_VARROCK_SWORD_DUMMY" + ], + "uvType": "BOX", + "uvScale": 0.35, + "colorOverrides": [ + { + "description": "Frame", + "colors": "h == 5 && s == 7 || h == 7", + "baseMaterial": "WOOD_GRAIN_3", + "shiftLightness": -2, + "uvType": "BOX", + "uvScale": 0.65 + }, + { + "description": "Rope", + "colors": "h == 0", + "baseMaterial": "METALLIC_1", + "shiftLightness": 10 + } + ] }, { "description": "Varrock Castle Railing - With Potted Plant", @@ -16474,7 +16571,7 @@ "uvScale": 0.8 }, { - "description": "Inconspicuous Rocks", + "description": "Inconspicuous Bush", "baseMaterial": "BARK", "objectIds": [ "HIDEY_UNBUILT_BUSH_BEGINNER", @@ -16509,8 +16606,11 @@ }, { "description": "Inconspicuous Crate", - "baseMaterial": "WOOD_GRAIN_3", + "windDisplacementMode": "VERTEX_JIGGLE", + "windDisplacementModifier": -2, "objectIds": [ + "HIDEY_UNBUILT_BUSH_BEGINNER", + "HIDEY_BUSH_BEGINNER", "HIDEY_UNBUILT_CRATE_EASY", "HIDEY_CRATE_EASY", "HIDEY_UNBUILT_CRATE_MEDIUM", @@ -16525,6 +16625,12 @@ "uvType": "BOX", "uvScale": 0.65, "colorOverrides": [ + { + "colors": "h == 6", + "baseMaterial": "WOOD_GRAIN_3", + "uvType": "BOX", + "uvScale": 0.65 + }, { "colors": "h > 6", "baseMaterial": "METALLIC_1_SMOOTH" @@ -21818,7 +21924,12 @@ }, { "description": "Objects - Ardougne Castle - Standard", - "baseMaterial": "GRUNGE_3_DARK", + "baseMaterial": "CARPET", + "uvType": "BOX", + "uvScale": 0.33, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "windDisplacementModifier": -2, "upwardsNormals": true, "objectIds": [ "STANDARD1_KANDARIN", @@ -21831,6 +21942,18 @@ "uvType": "BOX", "uvScale": 0.33, "uvOrientation": 512 + }, + { + "colors": "h == 0 && s == 0", + "baseMaterial": "CARPET", + "maxLightness": 37, + "upwardsNormals": true, + "uvType": "BOX", + "depthBias": 255, + "uvScale": 0.33, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "windDisplacementModifier": -2 } ] }, @@ -22096,13 +22219,25 @@ "uvScale": 0.8 }, { - "description": "Gnome - Wooden - Hammock - Textured", - "baseMaterial": "WOOD_GRAIN_3", - "uvType": "BOX", - "uvScale": 0.8, + "description": "Hammock", + "windDisplacementMode": "OBJECT", + "shiftLightness": -25, + "invertDisplacementStrength": true, + "flatNormals": true, + "windDisplacementModifier": -2, + "castShadows": false, "objectIds": [ "HAMMOCK", "HAMMOCK_SWINGING" + ], + "colorOverrides": [ + { + "description": "Wood", + "colors": "h == 5", + "baseMaterial": "WOOD_GRAIN_3", + "uvScale": 0.8, + "uvType": "BOX" + } ] }, { @@ -23193,7 +23328,8 @@ { "description": "Kharidian Saguaro Cactus", "baseMaterial": "PLANT_GRUNGE_1", - "windDisplacementMode": "VERTEX", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -3, "objectIds": [ "CACTUS2" ] @@ -23203,7 +23339,8 @@ "baseMaterial": "PLANT_GRUNGE_1", "uvType": "BOX", "uvScale": 0.8, - "windDisplacementMode": "VERTEX", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -3, "objectIds": [ "CACTUS4" ] @@ -23221,7 +23358,8 @@ { "description": "Kharidian Komodo Cactus", "baseMaterial": "PLANT_GRUNGE_2", - "windDisplacementMode": "VERTEX", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -3, "objectIds": [ "CACTUS", "DESERT_CACTUS_FULL", @@ -23231,7 +23369,8 @@ { "description": "Kharidian Sophmena Cactus", "baseMaterial": "PLANT_GRUNGE_2", - "windDisplacementMode": "VERTEX", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -3, "uvType": "BOX", "uvScale": 0.8, "objectIds": [ @@ -23518,7 +23657,11 @@ "colors": "s == 7", "baseMaterial": "LEAF_VEINS", "uvType": "BOX", - "uvScale": 0.2 + "uvScale": 0.2, + "undoVanillaShading": false, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -2, + "shiftLightness": 1 } ] }, @@ -29350,6 +29493,7 @@ "description": "Icyene Graveyard - Mausoleum ivy", "baseMaterial": "LEAF_VEINS", "windDisplacementMode": "VERTEX_JIGGLE", + "windDisplacementModifier": -3, "uvType": "MODEL_YZ", "uvOrientation": 144, "uvScale": 0.1, @@ -29358,7 +29502,9 @@ { "colors": "h == 17", "baseMaterial": "GRUNGE_1", - "flatNormals": true + "flatNormals": true, + "windDisplacementMode": "VERTEX_JIGGLE", + "windDisplacementModifier": -3 } ], "objectIds": [ @@ -29506,13 +29652,16 @@ "uvOrientation": 89, "uvType": "BOX", "uvScale": 0.8, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -3, "colorOverrides": [ { "colors": "h == 14", "baseMaterial": "LEAF_VEINS", "uvScale": 0.25, "uvType": "MODEL_XZ", - "windDisplacementMode": "VERTEX" + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -3 } ], "objectIds": [ @@ -30763,7 +30912,6 @@ "FERN", "FERN1", "FERN2", - "AREA_SANGUINE_OUTER_WALL_PLANT", "FAIRY2_PLANT_2", "FAIRY2_PLANT_4", "TLATI_PLANT_FERN01", @@ -30775,6 +30923,18 @@ "MISTMYST_ABIGALE_FERN_VIS" ] }, + { + "description": "Generic - Plant - Fern - On Wall", + "baseMaterial": "LEAF_VEINS", + "uvType": "MODEL_XZ", + "windDisplacementMode": "OBJECT", + "windDisplacementModifier": -2, + "invertDisplacementStrength": true, + "uvScale": 0.25, + "objectIds": [ + "AREA_SANGUINE_OUTER_WALL_PLANT" + ] + }, { "description": "Varlamore - Tlati Rainforest - Plant - Rock - Fern - Small", "baseMaterial": "LEAF_VEINS", @@ -35770,7 +35930,8 @@ "description": "Objects - Bush - Dead", "baseMaterial": "BARK", "uvScale": 0.4, - "windDisplacementMode": "VERTEX", + "windDisplacementMode": "OBJECT", + "windDisplacementModifier": -2, "objectIds": [ "LEAFLESSBUSH", "LEAFLESSBUSH2", @@ -38096,6 +38257,7 @@ { "description": "Textured Bushes", "windDisplacementMode": "VERTEX_JIGGLE", + "windDisplacementModifier": -2, "objectIds": [ "HEDGE", "HEDGECORNER", @@ -42434,13 +42596,70 @@ ] }, { - "description": "Port Khazard - Banner", + "description": "Port Khazard - Banner Tatty", + "baseMaterial": "CARPET", + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "undoVanillaShading": false, + "windDisplacementModifier": -2, + "upwardsNormals": true, + "depthBias": 100, + "objectIds": [ + "STANDARD_TATTY" + ], + "colorOverrides": [ + { + "colors": "h == 5 && s == 4", + "baseMaterial": "WOOD_GRAIN_3", + "uvType": "BOX", + "uvScale": 0.7 + } + ] + }, + { + "description": "Port Khazard - Banner - Zamorak Symbol", "baseMaterial": "CARPET_DARK", + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "undoVanillaShading": false, + "shiftLightness": 20, + "windDisplacementModifier": -1, "upwardsNormals": true, "objectIds": [ - "STANDARD_TATTY", "STANDARD_RED" ], + "colorOverrides": [ + { + "colors": "h == 5 && s == 4", + "baseMaterial": "WOOD_GRAIN_3", + "uvType": "BOX", + "uvScale": 0.7 + }, + { + "colors": "h == 9", + "baseMaterial": "CARPET_DARK", + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "undoVanillaShading": false, + "shiftLightness": 5, + "windDisplacementModifier": -1, + "upwardsNormals": true, + "depthBias": 100 + } + ] + }, + { + "description": "Scorpion Catcher Tower - Plain Banner", + "baseMaterial": "CARPET_DARK", + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "undoVanillaShading": false, + "shiftLightness": 17, + "windDisplacementModifier": -3, + "upwardsNormals": true, + "objectIds": [ + "STANDARD_RED_SCORPIAN_CATCHER" + ], "colorOverrides": [ { "colors": "h == 5 && s == 4", @@ -45055,6 +45274,8 @@ { "description": "Varlmaore - Auburnvale - Pine Tree 1", "baseMaterial": "LEAVES_1", + "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -2, "objectIds": [ "TREE_PINE_BRACKENTOP01", "TREE_PINE_BRACKENTOP02", @@ -45065,22 +45286,54 @@ "colors": "h == 3", "baseMaterial": "BARK", "uvType": "BOX", - "uvScale": 0.5 + "uvScale": 0.5, + "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -2 } ] }, { "description": "Varlmaore - Auburnvale - Pine Tree 2", "baseMaterial": "PLANT_GRUNGE_1", + "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -2, "objectIds": [ "TREE_PINE_DEFAULT02", "TREE_PINE_DEFAULT04", + "TREE_PINE_DEFAULT03", "TREE_PINE_DEFAULT01", "TREE_PINE_DEFAULT05" ], "colorOverrides": [ { - "colors": "h == 3", + "colors": "h == 3 && s == 2 || h == 3 && s == 3 && l < 15 || s == 7", + "baseMaterial": "BARK", + "uvType": "BOX", + "uvScale": 0.4, + "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -2 + }, + { + "colors": "h <= 12", + "baseMaterial": "PLANT_GRUNGE_1", + "uvType": "BOX", + "uvScale": 0.4, + "windDisplacementMode": "OBJECT", + "windDisplacementModifier": 3 + } + ] + }, + { + "description": "Varlmaore - Auburnvale - Stump", + "baseMaterial": "PLANT_GRUNGE_1", + "objectIds": [ + "TREE_PINE_STUMP01", + "TREE_PINE_STUMP02", + "TREE_PINE_BRACKENTOP01_STUMP01" + ], + "colorOverrides": [ + { + "colors": "h < 10", "baseMaterial": "BARK", "uvType": "BOX", "uvScale": 0.4 @@ -45092,6 +45345,8 @@ "baseMaterial": "BARK", "uvType": "BOX", "uvScale": 0.6, + "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -2, "objectIds": [ "TREE_PINE_DEAD02", "TREE_PINE_DEAD01" @@ -47245,13 +47500,13 @@ }, { "description": "Zeah - Varlamore - Plant - Withered Leafy Shrub", - "windDisplacementMode": "OBJECT", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", "baseMaterial": "PLANT_GRUNGE_1", "colorOverrides": [ { "colors": "h > 3", "baseMaterial": "LEAF_VEINS", - "windDisplacementMode": "OBJECT", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", "uvScale": 0.25, "uvType": "MODEL_XZ" } @@ -48786,6 +49041,12 @@ "baseMaterial": "CARPET_LIGHT", "uvType": "MODEL_XY", "uvScale": 0.2, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "undoVanillaShading": false, + "upwardsNormals": true, + "windDisplacementModifier": -2, + "shiftLightness": -2, "objectIds": [ "STANDARD_BLUE3" ], @@ -48795,9 +49056,219 @@ "baseMaterial": "WOOD_GRAIN_3", "uvType": "BOX", "uvScale": 0.5 + }, + { + "colors": [ "h == 0" ], + "baseMaterial": "CARPET_LIGHT", + "uvType": "MODEL_XY", + "uvScale": 0.2, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "undoVanillaShading": false, + "upwardsNormals": true, + "windDisplacementModifier": -2, + "depthBias": 100 } ] }, + { + "description": "Ardougne Combat Training Camp - Standards - Static", + "baseMaterial": "CARPET_LIGHT", + "uvType": "BOX", + "uvScale": 0.6, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "undoVanillaShading": false, + "upwardsNormals": true, + "windDisplacementModifier": -2, + "objectIds": [ + "STANDARD_BLUE2" + ], + "colorOverrides": [ + { + "colors": [ "h == 5" ], + "baseMaterial": "WOOD_GRAIN_3", + "uvType": "BOX", + "uvScale": 0.5 + }, + { + "colors": [ "h == 0" ], + "baseMaterial": "CARPET_LIGHT", + "uvType": "MODEL_XY", + "uvScale": 0.2, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "undoVanillaShading": false, + "upwardsNormals": true, + "windDisplacementModifier": -2, + "maxLightness": 65, + "depthBias": 100 + } + ] + }, + { + "description": "Wooden Door 1 - Fake Plank", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + "MCANNONDOOR", + "MCANNONDOOR1", + "MERLINWORKSHOP", + "FIGHTARENA_DOOR1", + "FIGHTARENA_DOOR2", + "IKOV_FIREWARRIORDOOR", + "IKOV_SHINYKEYDOOR", + "IKOV_LUCIENDOOR", + "POORDOOR", + "POORDOOR_M", + "POORDOOROPEN", + "POORDOOROPEN_M", + "INACTIVEPOORDOOR", + "MAGICGUILD_DOOR_L", + "MAGICGUILD_DOOR_R", + "LOCKEDDOOR1", + "MEMBERDOOR", + "POORDOOROPEN_PERM", + "MOURNERSTEWDOORUP", + "MOURNERSTEWDOORUPOPEN", + "MOURNERSTEWDOOR", + "MOURNERSTEWDOOROPEN", + "ELENADOOR2", + "WYDINDOOR", + "KHAZARD_STRONGHOLD_DOOR", + "BKFORTRESSDOOR1", + "BKFORTRESSDOOR2", + "BKFORTRESSDOOR3", + "INACBKFORTRESSDOOR", + "FAMCREST_DOORH2", + "FAMCREST_DOORH2I2", + "FAMCREST_DOORG2H1", + "FAMCREST_DOORI2H1", + "FAMCREST_DOORH2G1", + "ELENAGATESHUT", + "ELENAGATEOPEN", + "BRAVEKDOORSHUT", + "BRAVEKDOOROPEN", + "REHNISONDOORSHUT", + "BRAVEKDOORSHUT", + "BRAVEKDOOROPEN", + "PETE_TREASUREDOOR", + "PETE_SIDEDOOR", + "GRUBORDOOR", + "GARVDOOR", + "HEROKITCHENDOOR", + "WITCHSHEDDOOR", + "CAVEWITCH_DOOR", + "WITCHDOOR", + "DEATH_SHERPA_DOOR", + "DEATH_SHERPA_BACKDOOR", + "CASTLEWARS_SARADOMIN_SIDEDOOR", + "CASTLEWARS_SARADOMIN_SIDEDOOR_OPEN", + "CASTLEWARS_ZAMORAK_SIDEDOOR", + "CASTLEWARS_ZAMORAK_SIDEDOOR_OPEN", + "HAUNTEDMINE_BOSS_DOOR", + "ROGUESDEN_DOOR_TO_PUB", + "ROGUESDEN_DOOR_TO_PUB_OPEN", + "MOURNER_HIDEOUT_DOOR1", + "MOURNER_HIDEOUT_DOOR2", + "MOURNER_HIDEOUT_DOOR4", + "MOURNER_HIDEOUT_DOOR1_OPEN", + "MOURNER_HIDEOUT_DOOR2_OPEN", + "MOURNER_HIDEOUT_DOOR4_OPEN", + "MOURNING_POORDOOR", + "MOURNING_POORDOOROPEN", + "NEWBIE_DOOR1", + "NEWBIE_DOOR2", + "NEWBIE_DOOR3", + "NEWBIE_DOOR4", + "NEWBIE_DOOR6", + "NEWBIE_DOOR7", + "NEWBIE_DOOR8", + "NEWBIE_DOOR9", + "FAI_VARROCK_COOK_GUILD_DOOR", + "MAKINGHISTORY_DOOR", + "MAKINGHISTORY_DOOR_OPEN", + "DRAYNOR_BASEMENT_DOOR_OPEN", + "PICKLOCK1", + "PICKLOCK2", + "PICKLOCK4", + "PICKLOCK6", + "TOOLLOCK3", + "TOOLLOCK5", + "FAI_VARROCK_DOOR_OPEN", + "FAI_VARROCK_DOOR", + "FAI_VARROCK_DOOR_OPEN_TALLER", + "FAI_VARROCK_DOOR_TALLER", + "FAI_VARROCK_BANK_LOCKED_DOOR", + "HUNDRED_LUMBRIDGE_DOUBLEDOORL", + "HUNDRED_LUMBRIDGE_DOUBLEDOORR", + "QIP_SHEEP_SHEARER_POORDOOR", + "QIP_SHEEP_SHEARER_POORDOOROPEN", + "POORDOOR_DOUBLE_INNER", + "OPENPOORDOOR_DOUBLE_INNER", + "POORDOOR_DOUBLER_INNER", + "OPENPOORDOOR_DOUBLER_INNER", + "WILD_DOOR", + "WILD_DOOR_OPEN", + "DTTD_HAM_DOOR", + "SLICE_OUTPOST_POORDOOR", + "SLICE_OUTPOST_OPENPOORDOOR", + "SLICE_DOOR", + "SLICE_OPENDOOR", + "BLACKARMDOOR_OPEN", + "FAI_WIZTOWER_POOR_DOOR", + "FAI_WIZTOWER_POOR_DOOR_OPEN", + "FAI_FALADOR_POOR_DOOR", + "FAI_FALADOR_POOR_DOOR_OPEN", + "FAI_FALADOR_POOR_DOOR_CLOSED_M", + "FAI_FALADOR_POOR_DOOR_OPEN_M", + "FAI_FALADOR_POOR_DOOR_RR", + "FAI_FALADOR_POOR_DOOR_OPEN_TALLER", + "FAI_FALADOR_POOR_DOOR_TALLER", + "FAI_FALADOR_POOR_CASTLE_DOOR", + "FAI_FALADOR_POOR_CASTLE_DOOR_OPEN", + "FAI_FALADOR_POOR_DOOR_NOOP", + "CANAFIS_DOOR_OPEN", + "CANAFIS_DOOR_OFFSET", + "CANAFIS_BANK_DOOR", + "CANAFIS_DOOR_GROUND_OPEN", + "CANAFIS_DOOR_GROUND", + "CHEFDOOR", + "QIP_OBS_RECEPTION_DOOR_OPEN", + "QIP_OBS_RECEPTION_DOOR", + "QIP_COOK_DOOR", + "KR_SIN_POORDOOR", + "KR_SIN_POORDOOROPEN", + "KR_POORDOOR", + "KR_POORDOOROPEN", + "BR_DOOR_LOCKED", + "BR_DOOR_FORFEIT", + "TUT2_SURVIVAL_ENTRY", + "TUT2_NAV_ENTRY", + "MGUILD_DOOR", + "MGUILD_DOOR_INNER1", + "MGUILD_DOOR_INNER2", + "POORDOOR_VIS", + "POORDOOR_OPEN_VIS", + "POORDOOR_NOOP", + "PLAGUEELENADOORSHUT_VIS", + "PLAGUEELENADOOROPEN_VIS", + "TUT2_NAV_EXIT", + "TUT2_QUEST_ENTRY", + "TUT2_BANK_EXIT", + "CON_CONTRACT_ARDY_WEST_DOOR_LOCKED", + "FAI_FALADOR_POOR_DOOR_INACTIVE", + "CANAFIS_DOOR_GROUND_NOOP", + "FAI_FALADOR_POOR_DOOR_FLIP", + "FAI_FALADOR_POOR_DOOR_OPEN_FLIP", + "FIGHTARENA_DOOR2_ESCAPE", + "DOTI_INTERROGATION_DOOR", + "LUC2_MOV_DOOR_CLOSED", + "LUC2_MOV_DOOR_OPEN" + ], + "uvType": "BOX", + "flatNormals": true, + "uvScale": 0.65 + }, { "description": "Lumbridge Castle - Standards - Animated", "baseMaterial": "GRAY_80", @@ -49891,6 +50362,8 @@ { "description": "Tent - High poly mesh", "baseMaterial": "CARPET", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -1, "objectIds": [ "SUROK_BANDIT_CAMP_TENT", "SUROK_BANDIT_CAMP_TENT_DIAG", @@ -65100,39 +65573,6 @@ "SANCTUARY_STATUE" ] }, - { - "description": "Decoration - Wall - Saradomin wall flag", - "baseMaterial": "WOOD_GRAIN_3", - "uvType": "BOX", - "uvOrientation": 512, - "objectIds": [ - "FAI_VARROCK_WALLHANGING_SARODOMIN_STAR" - ], - "colorOverrides": [ - { - "description": "Gold fabric", - "colors": "s == 7", - "baseMaterial": "CARPET_DARK", - "uvType": "MODEL_YZ", - "upwardsNormals": true - }, - { - "description": "White fabric", - "colors": "h > 10", - "baseMaterial": "CARPET_DARK", - "uvOrientation": 280, - "uvType": "MODEL_YZ", - "upwardsNormals": true - }, - { - "description": "Star", - "colors": "s == 0", - "baseMaterial": "CARPET_DARK", - "uvType": "MODEL_YZ", - "upwardsNormals": true - } - ] - }, { "description": "Varrock - Vault - Golden Bars - Stacked", "baseMaterial": "METALLIC_1_GLOSS", @@ -68114,6 +68554,47 @@ "baseMaterial": "WOOD_GRAIN_3", "uvType": "BOX", "uvOrientationX": 512, + "objectIds": [ + "FAI_VARROCK_PALACE_WALLHANGING", + "FAI_VARROCK_WALLHANGING_SARODOMIN_STAR" + ], + "colorOverrides": [ + { + "description": "Yellow fabric", + "colors": "s == 7", + "baseMaterial": "CARPET", + "uvType": "MODEL_YZ", + "shiftLightness": 8, + "minLightness": 13, + "upwardsNormals": true, + "undoVanillaShading": false + }, + { + "description": "Black fabric", + "colors": "h ==0 && s == 0", + "baseMaterial": "CARPET", + "uvType": "MODEL_YZ", + "depthBias": 100, + "upwardsNormals": true, + "undoVanillaShading": false + }, + { + "description": "White fabric", + "colors": "h == 42", + "baseMaterial": "CARPET", + "uvType": "MODEL_YZ", + "minLightness": 30, + "upwardsNormals": true, + "undoVanillaShading": false + } + ] + }, + { + "description": "Varrock - Decoration - Wall - Varrock Castle wall flag - Outdoors with Wind Displacement", + "areas": [ "VARROCK_CASTLE_OUTDOOR_BANNERS" ], + "baseMaterial": "WOOD_GRAIN_3", + "uvType": "BOX", + "uvOrientationX": 512, "objectIds": [ "FAI_VARROCK_PALACE_WALLHANGING" ], @@ -68121,29 +68602,44 @@ { "description": "Yellow fabric", "colors": "s == 7", - "baseMaterial": "CARPET_DARK", + "baseMaterial": "CARPET", "uvType": "MODEL_YZ", - "upwardsNormals": true + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "invertDisplacementStrength": true, + "windDisplacementModifier": 2, + "shiftLightness": 8, + "minLightness": 13, + "upwardsNormals": true, + "undoVanillaShading": false }, { "description": "Black fabric", - "colors": "s == 0", - "baseMaterial": "CARPET_DARK", + "colors": "h ==0 && s == 0", + "baseMaterial": "CARPET", "uvType": "MODEL_YZ", - "upwardsNormals": true + "depthBias": 100, + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "invertDisplacementStrength": true, + "windDisplacementModifier": 2, + "upwardsNormals": true, + "undoVanillaShading": false }, { "description": "White fabric", "colors": "h == 42", - "baseMaterial": "CARPET_DARK", - "uvOrientation": 280, + "baseMaterial": "CARPET", "uvType": "MODEL_YZ", - "upwardsNormals": true + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "invertDisplacementStrength": true, + "windDisplacementModifier": 2, + "minLightness": 30, + "upwardsNormals": true, + "undoVanillaShading": false } ] }, { - "description": "Varrock - Decoration - Varrock Castle Standing flag", + "description": "Varrock - Decoration - Varrock Castle Standing Flag", "baseMaterial": "WOOD_GRAIN_3", "uvType": "BOX", "objectIds": [ @@ -68154,20 +68650,64 @@ "description": "Yellow fabric", "colors": "s == 7", "baseMaterial": "CARPET", - "uvType": "MODEL_XY" + "uvType": "MODEL_XY", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "invertDisplacementStrength": true, + "minLightness": 13 }, { "description": "Black fabric", - "colors": "s == 0", + "colors": "h ==0 && s == 0", "baseMaterial": "CARPET", - "uvType": "MODEL_XY" + "uvType": "MODEL_XY", + "depthBias": 100, + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "invertDisplacementStrength": true }, { "description": "White fabric", "colors": "h == 42", "baseMaterial": "CARPET", - "uvOrientation": 280, - "uvType": "MODEL_XY" + "uvType": "MODEL_XY", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "invertDisplacementStrength": true, + "shiftLightness": -15, + "minLightness": 30 + } + ] + }, + { + "description": "Varrock - Decoration - Varrock Old Standing Flag", + "baseMaterial": "WOOD_GRAIN_3", + "uvType": "BOX", + "objectIds": [ + "STANDARD1_VARROCK" + ], + "colorOverrides": [ + { + "description": "Yellow fabric", + "colors": "h == 5 && s == 7", + "baseMaterial": "CARPET", + "uvType": "MODEL_XY", + "depthBias": 100, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "upwardsNormals": true, + "undoVanillaShading": false, + "windDisplacementModifier": -2, + "shiftLightness": -4 + }, + { + "description": "Black fabric", + "colors": "h == 19", + "baseMaterial": "CARPET", + "uvType": "MODEL_XY", + "upwardsNormals": true, + "undoVanillaShading": false, + "shiftLightness": -2, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "windDisplacementModifier": -2 } ] }, @@ -68348,6 +68888,46 @@ } ] }, + { + "description": "Varrock - Hanging Plants", + "baseMaterial": "GRUNGE_3", + "uvType": "BOX", + "uvScale": 0.35, + "uvOrientation": 42, + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "invertDisplacementStrength": true, + "windDisplacementModifier": -1, + "objectIds": [ + "FAI_VARROCK_HANGING_PLANT1", + "FAI_VARROCK_HANGING_PLANT2", + "FAI_VARROCK_HANGING_PLANT3" + ], + "colorOverrides": [ + { + "colors": "h == 7 && s < 7", + "baseMaterial": "WOOD_GRAIN_3", + "uvType": "BOX", + "uvOrientation": 512, + "uvScale": 0.3 + }, + { + "colors": "h == 20 || h == 21", + "baseMaterial": "PLANT_GRUNGE_1", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "invertDisplacementStrength": true, + "windDisplacementModifier": -1 + }, + { + "colors": "h == 7 || h == 10", + "baseMaterial": "DIRT_1", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "invertDisplacementStrength": true, + "windDisplacementModifier": -1, + "uvScale": 0.3, + "uvType": "MODEL_XZ" + } + ] + }, { "description": "Objects - Table - Single - Wooden – Plank - Generic", "baseMaterial": "WOOD_GRAIN_3", @@ -68504,6 +69084,8 @@ "description": "Varrock - Castle Tree", "baseMaterial": "BARK", "uvType": "BOX", + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -3, "uvScale": 0.75, "objectIds": [ "FAI_VARROCK_COURTYARD_TREE_BASE", @@ -68514,6 +69096,8 @@ "description": "Leaves", "colors": "h > 10", "baseMaterial": "GRUNGE_3_DARK", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -3, "uvType": "BOX", "uvScale": 0.3 } @@ -68630,6 +69214,8 @@ "description": "Varrock - Castle Garden - Stone - Dirt - Flower - Orchids in Stone Pots", "baseMaterial": "GRUNGE_3", "uvType": "BOX", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -2, "objectIds": [ "GARDEN_ORCHIDS_FULLYGROWN", "GARDEN_ORCHIDS_FULLYGROWN_YELLOW", @@ -68655,7 +69241,9 @@ "colors": "h == 24", "baseMaterial": "LEAF_VEINS", "uvType": "BOX", - "uvScale": 0.4 + "uvScale": 0.4, + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -2 } ] }, @@ -68704,6 +69292,8 @@ "uvType": "BOX", "uvOrientation": 89, "uvScale": 0.8, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -3, "objectIds": [ "GARDEN_VINES_FULLYGROWN" ], @@ -68712,20 +69302,26 @@ "description": "Grapes", "colors": "h == 50", "baseMaterial": "GRUNGE_3_SHINY", + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -3, "uvType": "BOX" }, { "description": "Leaves", "colors": "h == 16", "baseMaterial": "LEAF_VEINS", + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -3, "uvType": "BOX", - "uvScale": 0.4 + "uvScale": 0.25 } ] }, { "description": "Varrock - Castle Garden - Flower - Snow Drops", "baseMaterial": "GRUNGE_3", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -2, "uvType": "BOX", "uvOrientation": 89, "uvScale": 0.4, @@ -68738,13 +69334,17 @@ "colors": "h == 19", "baseMaterial": "LEAF_VEINS", "uvType": "BOX", - "uvScale": 0.6 + "uvScale": 0.6, + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -2 } ] }, { "description": "Varrock - Castle Garden - Flower - Delphiniums", "baseMaterial": "GRUNGE_3_DARK", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -2, "uvType": "BOX", "objectIds": [ "GARDEN_DELPHINIUMS_FULLYGROWN" @@ -68755,13 +69355,17 @@ "colors": "h == 21", "baseMaterial": "LEAF_VEINS", "uvType": "BOX", - "uvScale": 0.6 + "uvScale": 0.6, + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -2 } ] }, { "description": "Varrock - Hosidius - Castle Garden - Flower - Rose Bushes", "baseMaterial": "GRUNGE_3", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -2, "uvType": "BOX", "objectIds": [ "GARDEN_ROSEBUSH_PINK_FULLYGROWN", @@ -68776,6 +69380,8 @@ "description": "Leaves", "colors": "h == 21", "baseMaterial": "LEAF_VEINS", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -2, "uvType": "BOX", "uvScale": 0.6 } @@ -68811,20 +69417,46 @@ } ] }, + { + "description": "Zeah - Kourend - Hosidius - Rocks with Flowers", + "objectIds": [ + "ZIG_ROCKERY_4X4_01", + "ZIG_ROCKERY_4X4_02", + "ZIG_ROCKERY_1X1_02", + "ZIG_ROCKERY_1X1_01", + "ZIG_ROCKERY_2X2", + "ZIG_ROCKERY_1X1_03" + ], + "colorOverrides": [ + { + "colors": "h >= 0", + "baseMaterial": "GRAY_90", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -2 + } + ] + }, { "description": "Zeah - Kourend - Hosidius - Flower - Rose", "baseMaterial": "GRUNGE_3", "uvType": "BOX", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -1, "objectIds": [ - "KOURENDWOODLAND_ROSES" + "KOURENDWOODLAND_ROSES", + "KEBOS_FARMING_GUILD_FLOWERS_ROSES_01", + "AKD_HUGHES_ROSES_1_NOOP" ], "colorOverrides": [ { "description": "Leaves", "colors": "h == 17", - "baseMaterial": "LEAF_VEINS", + "baseMaterial": "PLANT_GRUNGE_2", + "shiftLightness": -8, + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -1, "uvType": "BOX", - "uvScale": 0.6 + "uvScale": 0.2 } ] }, @@ -69415,6 +70047,8 @@ { "description": "Generic - Cabbage", "baseMaterial": "LEAF_VEINS", + "windDisplacementMode": "OBJECT", + "windDisplacementModifier": -3, "objectIds": [ "CABBAGE", "AVIUM_CABBAGE01", @@ -72901,15 +73535,54 @@ }, { "description": "Gnome Stronghold - Cloth - Wooden - Standards", - "baseMaterial": "CARPET_DARK", + "baseMaterial": "CARPET", + "shiftLightness": -3, "uvType": "MODEL_XY", "uvScale": 0.5, + "undoVanillaShading": false, "upwardsNormals": true, "terrainVertexSnap": true, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "windDisplacementModifier": -1, "objectIds": [ "GNOMESTANDARD2", "GNOMESTANDARD1" ], + "colorOverrides": [ + { + "colors": [ "h == 5", "h == 6" ], + "baseMaterial": "WOOD_GRAIN_2_SMOOTH", + "uvType": "BOX", + "uvOrientation": 512, + "uvScale": 0.5 + }, + { + "colors": [ "h == 16 && l == 51 || h == 16 && l == 2" ], + "baseMaterial": "CARPET_DARK", + "uvType": "MODEL_XY", + "uvScale": 0.5, + "upwardsNormals": true, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "invertDisplacementStrength": true, + "depthBias": 100, + "windDisplacementModifier": -1 + } + ] + }, + { + "description": "Gnome Stronghold - Cloth - Wooden - Standards - No Wind", + "areas": [ "TREE_GNOME_STRONGHOLD_BANNERS_NO_WIND" ], + "baseMaterial": "CARPET", + "shiftLightness": -2, + "uvType": "MODEL_XY", + "uvScale": 0.5, + "undoVanillaShading": false, + "upwardsNormals": true, + "terrainVertexSnap": true, + "objectIds": [ + "GNOMESTANDARD2" + ], "colorOverrides": [ { "colors": [ "h == 5", "h == 6" ], @@ -83090,6 +83763,8 @@ "doubleSidedFaces": true, "uvType": "BOX", "uvScale": 0.25, + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -2, "objectIds": [ "PALM_UPDATE01", "PALM_UPDATE02", @@ -83105,7 +83780,9 @@ "baseMaterial": "LIGHT_BARK_SMOOTH", "uvType": "BOX", "uvScale": 0.5, - "uvOrientation": 512 + "uvOrientation": 512, + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -2 } ] }, @@ -85106,6 +85783,8 @@ { "description": "Slepe - Plant - Red Flower", "baseMaterial": "GRUNGE_3", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -2, "uvType": "BOX", "uvOrientation": -272, "uvScale": 0.4, @@ -85115,7 +85794,9 @@ "colors": "h == 21", "baseMaterial": "LEAF_VEINS", "uvType": "BOX", - "uvScale": 0.5 + "uvScale": 0.5, + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "windDisplacementModifier": -2 } ], "objectIds": [ @@ -86866,50 +87547,66 @@ "description": "Ardougne - Red, Yellow, and blue flowers and tulips", "baseMaterial": "GRAY_90", "windDisplacementMode": "OBJECT", + "windDisplacementModifier": -1, "colorOverrides": [ { "description": "Leaves", "colors": "h == 21", - "baseMaterial": "LEAF_VEINS", + "baseMaterial": "GRUNGE_3", + "minLightness": 7, "uvType": "BOX", - "uvScale": 0.6 + "uvScale": 0.15, + "windDisplacementMode": "OBJECT", + "windDisplacementModifier": -1 } ], "objectIds": [ "RAREFLOWER_1", "RAREFLOWERS_1", "RAREFLOWERS_BLUE", - "TULIPS" + "TULIPS", + "REGICIDE_RAREFLOWER_2", + "REGICIDE_RAREFLOWERS_1", + "REGICIDE_RAREFLOWER_1" ] }, { "description": "Ardougne - Sunflowers", "baseMaterial": "GRAY_90", - "windDisplacementMode": "VERTEX", + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", "colorOverrides": [ { "description": "Leaves", "colors": "h == 21", - "baseMaterial": "LEAF_VEINS", + "baseMaterial": "GRUNGE_3", + "shiftLightness": -2, "uvType": "BOX", - "uvScale": 0.6 + "windDisplacementMode": "OBJECT_NO_GROUND_DISPLACEMENT", + "uvScale": 0.2 } ], "objectIds": [ - "SUNFLOWERS" + "SUNFLOWER", + "SUNFLOWERS", + "QIP_OBS_FLOWERA1_BIG", + "QIP_OBS_FLOWERA1_SMALL" ] }, { "description": "Ardougne - small Red and blue flowers and Daisies", "baseMaterial": "GRAY_90", "windDisplacementMode": "OBJECT", + "depthBias": 50, + "terrainVertexSnap": true, "colorOverrides": [ { "description": "Leaves", "colors": "h == 14", - "baseMaterial": "LEAF_VEINS", + "baseMaterial": "GRUNGE_3", + "shiftLightness": -2, "uvType": "BOX", - "uvScale": 0.6 + "uvScale": 0.2, + "windDisplacementMode": "OBJECT" } ], "objectIds": [ @@ -88326,7 +89023,8 @@ ], "objectIds": [ "PLANT_TROPICAL_BROMELIA01", - "TT_HUNTING_PLANT_03_OP" + "TT_HUNTING_PLANT_03_OP", + "TT_HUNTING_PLANT_03_NOOP" ] }, { @@ -88376,6 +89074,7 @@ { "description": "Tropical - Great Conch - Tropcial Palm", "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -1, "baseMaterial": "LEAF_VEINS_PALM", "uvScale": 0.25, "uvType": "BOX", @@ -88392,6 +89091,7 @@ { "colors": "h == 5 || h == 9", "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -1, "baseMaterial": "PLANT_GRUNGE_2", "uvScale": 0.2, "uvType": "BOX" @@ -88401,6 +89101,7 @@ { "description": "Tropical - Great Conch - Tropcial Palm - Purple and red", "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -1, "baseMaterial": "LEAF_VEINS_PALM", "uvScale": 0.2, "uvType": "BOX", @@ -88412,6 +89113,8 @@ { "colors": "h == 58 && s > 3", "baseMaterial": "PLANT_GRUNGE_2", + "windDisplacementMode": "VERTEX", + "windDisplacementModifier": -1, "uvScale": 0.4, "uvType": "BOX" } @@ -90029,9 +90732,17 @@ ] }, { - "description": "Hot Air Balloon - Disable Detail Culling", + "description": "Hot Air Balloon Basket - Disable Detail Culling", + "objectIds": [ + "ZEP_BASKET" + ], + "disableDetailCulling": true + }, + { + "description": "Hot Air Balloon - Disable Detail Culling & Add Wind Dispacement", + "windDisplacementMode": "VERTEX_WITH_HEMISPHERE_BLEND", + "windDisplacementModifier": -1, "objectIds": [ - "ZEP_BASKET", "ZEP_BALLOON" ], "disableDetailCulling": true @@ -91177,18 +91888,17 @@ "uvOrientation": 89, "uvScale": 0.5, "windDisplacementMode": "OBJECT", - "castShadows": false, "objectIds": [ "THISTLE" ], "colorOverrides": [ { "colors": "h == 16", - "baseMaterial": "LEAF_VEINS", + "baseMaterial": "GRUNGE_3", + "shiftLightness": -1, "uvType": "BOX", - "uvScale": 0.5, + "uvScale": 0.2, "windDisplacementMode": "OBJECT", - "castShadows": false, "terrainVertexSnap": true } ] diff --git a/src/main/resources/rs117/hd/scene_frag.glsl b/src/main/resources/rs117/hd/scene_frag.glsl index a4bacf0f40..75cea84508 100644 --- a/src/main/resources/rs117/hd/scene_frag.glsl +++ b/src/main/resources/rs117/hd/scene_frag.glsl @@ -32,10 +32,13 @@ #define DISPLAY_SHADOWS 0 #define DISPLAY_LIGHTING 0 +#define NEAR_PLANE_DITHER_START 0.2 + #include #include #include #include +#include #include MATERIAL_CONSTANTS @@ -50,8 +53,14 @@ flat in ivec3 fAlphaBiasHsl; flat in ivec3 fMaterialData; flat in ivec3 fTerrainData; -#if FLAT_SHADING && ZONE_RENDERER - flat in vec3 fFlatNormal; +#if ZONE_RENDERER + #if FLAT_SHADING + flat in vec3 fFlatNormal; + #endif + + #if DITHER_FADE + flat in float fFade; + #endif #endif in FragmentData { @@ -84,6 +93,18 @@ vec2 worldUvs(float scale) { void main() { vec3 downDir = vec3(0, -1, 0); + + #if ZONE_RENDERER && DITHER_FADE + float viewZ = 1.0 - gl_FragCoord.z; + if (fFade > 0.0 || viewZ < NEAR_PLANE_DITHER_START) { + float fadeAmount = mix(1.0 - saturate(viewZ / NEAR_PLANE_DITHER_START), fFade, saturate(fFade)); + float threshold = smoothstep(0.0, 1.0, pow(fadeAmount, 1.35)); + float noise = interleavedGradientNoise(gl_FragCoord.xy); + if (noise < threshold) + discard; + } + #endif + // View & light directions are from the fragment to the camera/light vec3 viewDir = normalize(cameraPos - IN.position); diff --git a/src/main/resources/rs117/hd/scene_vert.glsl b/src/main/resources/rs117/hd/scene_vert.glsl index 8c55ea53e0..a1d9835072 100644 --- a/src/main/resources/rs117/hd/scene_vert.glsl +++ b/src/main/resources/rs117/hd/scene_vert.glsl @@ -27,20 +27,26 @@ #include #include +#include +#include +#include #include #include +#include +#include layout (location = 0) in vec3 vPosition; #if ZONE_RENDERER layout (location = 1) in vec4 vUv; layout (location = 2) in vec4 vNormal; - layout (location = 3) in int vTextureFaceIdx; + layout (location = 3) in int vPackedTextureFace; layout (location = 6) in int vWorldViewId; layout (location = 7) in ivec2 vSceneBase; - - uniform isamplerBuffer textureFaces; + #if DITHER_FADE + layout (location = 8) in float vFade; + #endif #else layout (location = 1) in vec3 vUv; layout (location = 2) in vec3 vNormal; @@ -51,6 +57,9 @@ layout (location = 0) in vec3 vPosition; #if ZONE_RENDERER flat out int fWorldViewId; + #if DITHER_FADE + flat out float fFade; + #endif flat out ivec3 fAlphaBiasHsl; flat out ivec3 fMaterialData; flat out ivec3 fTerrainData; @@ -68,52 +77,80 @@ layout (location = 0) in vec3 vPosition; void main() { int vertex = gl_VertexID % 3; - bool isProvoking = vertex == 2; - - int faceIdx = vTextureFaceIdx & 0x7FFFFFFF; - bool windingReversed = vTextureFaceIdx < 0; - if (windingReversed) + if (isFaceWindingReversed(vPackedTextureFace)) vertex = 2 - vertex; int materialData = 0; int alphaBiasHsl = 0; - if (isProvoking) { - // Only the Provoking vertex needs to fetch the face data - fAlphaBiasHsl = texelFetch(textureFaces, faceIdx).xyz; - fMaterialData = texelFetch(textureFaces, faceIdx + 1).xyz; - fWorldViewId = vWorldViewId; - alphaBiasHsl = fAlphaBiasHsl[vertex]; - materialData = fMaterialData[vertex]; + if (isModelFace(vPackedTextureFace)) { + ModelFaceData faceData = getModelFaceData(getFaceOffset(vPackedTextureFace)); + fAlphaBiasHsl = faceData.AlphaBiasHsl; + fMaterialData = ivec3(faceData.MaterialData); + fTerrainData = ivec3(0); + alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + materialData = faceData.MaterialData; } else { - // All outputs must be written to for macOS compatibility - fAlphaBiasHsl = ivec3(0); - fMaterialData = ivec3(0); - fWorldViewId = 0; - alphaBiasHsl = texelFetch(textureFaces, faceIdx)[vertex]; - materialData = texelFetch(textureFaces, faceIdx + 1)[vertex]; + StaticFaceData faceData = getStaticFaceData(getFaceOffset(vPackedTextureFace)); + fAlphaBiasHsl = faceData.AlphaBiasHsl; + fMaterialData = faceData.MaterialData; + fTerrainData = faceData.TerrainData; + alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + materialData = faceData.MaterialData[vertex]; } - fTerrainData = texelFetch(textureFaces, faceIdx + 2).xyz; + #if DITHER_FADE + fFade = vFade; + #endif + + int worldViewIdx = vWorldViewId; vec3 sceneOffset = vec3(vSceneBase.x, 0, vSceneBase.y); + ModelData modelData; + + int modelIdx = int(vNormal.w); + if (modelIdx > 0) { + modelData = getModelData(modelIdx); + #if DITHER_FADE + fFade = max(modelData.fade, fFade); + #endif + if (isModelDynamic(modelData)) { + worldViewIdx = modelData.worldViewIdx; + sceneOffset = modelData.position; + } + } + vec3 worldNormal = vNormal.xyz; - vec3 worldPosition = sceneOffset + vPosition; - if (vWorldViewId != -1) { - mat4x3 worldViewProjection = mat4x3(getWorldViewProjection(vWorldViewId)); - worldPosition = worldViewProjection * vec4(worldPosition, 1.0); - worldNormal = mat3(worldViewProjection) * worldNormal; + vec3 worldPosition = vPosition + sceneOffset; + + if (modelIdx > 0) { + ObjectWindSample windSample = computeWindSample(modelData.position, modelData.height); + worldPosition += applyWindDisplacementVertex( + windSample, + materialData, + float(modelData.height), + worldPosition, + worldPosition - modelData.position, + vNormal.xyz + ); + } else { + // Clamp underwater vertices to the water surface along the draw distance border, excluding + // waterDepth == 1, which is used when the geometry already sits flush with the surface + int waterDepth = fTerrainData[vertex] >> 11 & 0xFFF; + if (waterDepth > 1) { + const int TILE_SIZE = 128; + const int CHUNK_SIZE = TILE_SIZE * 8; + ivec2 cam = ivec2(cameraPos.xz / CHUNK_SIZE) * CHUNK_SIZE + CHUNK_SIZE / 2; + ivec2 d = ivec2(abs(worldPosition.xz - cam) / TILE_SIZE); + if (max(d.x, d.y) > int(drawDistance / 8) * 8 + 3) + worldPosition.y -= waterDepth; + } } - // Clamp underwater vertices to the water surface along the draw distance border, excluding - // waterDepth == 1, which is used when the geometry already sits flush with the surface - int waterDepth = fTerrainData[vertex] >> 11 & 0xFFF; - if (waterDepth > 1) { - const int TILE_SIZE = 128; - const int CHUNK_SIZE = TILE_SIZE * 8; - ivec2 cam = ivec2(cameraPos.xz / CHUNK_SIZE) * CHUNK_SIZE + CHUNK_SIZE / 2; - ivec2 d = ivec2(abs(worldPosition.xz - cam) / TILE_SIZE); - if (max(d.x, d.y) > int(drawDistance / 8) * 8 + 3) - worldPosition.y -= waterDepth; + if (worldViewIdx != -1) { + mat4x3 worldViewProjection = mat4x3(getWorldViewProjection(worldViewIdx)); + worldPosition = worldViewProjection * vec4(worldPosition, 1.0); + worldNormal = mat3(worldViewProjection) * worldNormal; } + fWorldViewId = worldViewIdx; OUT.position = worldPosition; OUT.uv = computeVertexUvs(materialData, worldPosition, vUv.xyz); diff --git a/src/main/resources/rs117/hd/shadow_vert.glsl b/src/main/resources/rs117/hd/shadow_vert.glsl index 08425a1898..f75f8698e5 100644 --- a/src/main/resources/rs117/hd/shadow_vert.glsl +++ b/src/main/resources/rs117/hd/shadow_vert.glsl @@ -28,18 +28,25 @@ #include #include #include +#include +#include +#include #include +#include +#include layout (location = 0) in vec3 vPosition; #if ZONE_RENDERER layout (location = 1) in vec4 vUv; - layout (location = 3) in int vTextureFaceIdx; + layout (location = 2) in vec4 vNormal; + layout (location = 3) in int vPackedTextureFace; layout (location = 6) in int vWorldViewId; layout (location = 7) in ivec2 vSceneBase; - - uniform isamplerBuffer textureFaces; + #if DITHER_FADE + layout (location = 8) in float vFade; + #endif #if SHADOW_MODE == SHADOW_MODE_DETAILED out vec4 fUvw; @@ -52,19 +59,52 @@ layout (location = 0) in vec3 vPosition; void main() { int vertex = gl_VertexID % 3; - - int faceIdx = vTextureFaceIdx & 0x7FFFFFFF; - bool windingReversed = vTextureFaceIdx < 0; - if (windingReversed) + if (isFaceWindingReversed(vPackedTextureFace)) vertex = 2 - vertex; - int alphaBiasHsl = texelFetch(textureFaces, faceIdx)[vertex]; - int materialData = texelFetch(textureFaces, faceIdx + 1)[vertex]; - int terrainData = texelFetch(textureFaces, faceIdx + 2)[vertex]; + int alphaBiasHsl; + int materialData; + int terrainData; + int faceDataOffset; + if (isModelFace(vPackedTextureFace)) { + ModelFaceData faceData = getModelFaceData(getFaceOffset(vPackedTextureFace)); + alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + materialData = faceData.MaterialData; + terrainData = 0; + } else { + StaticFaceData faceData = getStaticFaceData(getFaceOffset(vPackedTextureFace)); + alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + materialData = faceData.MaterialData[vertex]; + terrainData = faceData.TerrainData[vertex]; + } + + int worldViewIdx = vWorldViewId; + vec3 sceneOffset = vec3(vSceneBase.x, 0, vSceneBase.y); + ModelData modelData; int waterTypeIndex = terrainData >> 3 & 0xFF; float opacity = 1 - (alphaBiasHsl >> 24 & 0xFF) / float(0xFF); + #if DITHER_FADE + float fade = vFade; + #endif + + int modelIdx = int(vNormal.w); + if (modelIdx > 0) { + modelData = getModelData(modelIdx); + #if DITHER_FADE + fade = modelData.fade; + #endif + if (isModelDynamic(modelData)) { + worldViewIdx = modelData.worldViewIdx; + sceneOffset = modelData.position; + } + } + + #if DITHER_FADE + opacity *= saturate(1 - fade); + #endif + float opacityThreshold = float(materialData >> MATERIAL_SHADOW_OPACITY_THRESHOLD_SHIFT & 0x3F) / 0x3F; if (opacityThreshold == 0) opacityThreshold = SHADOW_DEFAULT_OPACITY_THRESHOLD; @@ -100,13 +140,24 @@ layout (location = 0) in vec3 vPosition; int shouldCastShadow = isShadowDisabled ? 0 : 1; - vec3 sceneOffset = vec3(vSceneBase.x, 0, vSceneBase.y); vec3 worldPosition = sceneOffset + vPosition; - if (vWorldViewId != -1) { - mat4x3 worldViewProjection = mat4x3(getWorldViewProjection(vWorldViewId)); + if (worldViewIdx != -1) { + mat4x3 worldViewProjection = mat4x3(getWorldViewProjection(worldViewIdx)); worldPosition = worldViewProjection * vec4(worldPosition, 1.0);; } + if (modelIdx > 0) { + ObjectWindSample windSample = computeWindSample(modelData.position, modelData.height); + worldPosition += applyWindDisplacementVertex( + windSample, + materialData, + float(modelData.height), + worldPosition, + worldPosition - modelData.position, + vNormal.xyz + ); + } + #if SHADOW_TRANSPARENCY fOpacity = opacity; #endif diff --git a/src/main/resources/rs117/hd/uniforms/compute.glsl b/src/main/resources/rs117/hd/uniforms/compute.glsl index b1b7a9a56d..218ddcf18b 100644 --- a/src/main/resources/rs117/hd/uniforms/compute.glsl +++ b/src/main/resources/rs117/hd/uniforms/compute.glsl @@ -19,5 +19,5 @@ layout(std140) uniform UBOCompute { float windOffset; int characterPositionCount; - vec3 characterPositions[MAX_CHARACTER_POSITION_COUNT]; + vec4 characterPositions[MAX_CHARACTER_POSITION_COUNT]; }; diff --git a/src/main/resources/rs117/hd/uniforms/displacement.glsl b/src/main/resources/rs117/hd/uniforms/displacement.glsl new file mode 100644 index 0000000000..ad87b34226 --- /dev/null +++ b/src/main/resources/rs117/hd/uniforms/displacement.glsl @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include MAX_CHARACTER_POSITION_COUNT +#include MAX_BOAT_COUNT + +#define BOAT_CONTOUR 8 +#define BOAT_DIST_OFFSET 42.0 + +struct Boat { + // packed XY half16 + ivec4 boatContour[BOAT_CONTOUR / 4]; +}; + +layout(std140) uniform UBODisplacement { + float windDirectionX; + float windDirectionZ; + float windStrength; + float windCeiling; + float windOffset; + + int characterPositionCount; + int boatCount; + + vec4 characterPositions[MAX_CHARACTER_POSITION_COUNT]; + Boat boats[MAX_BOAT_COUNT]; +}; + +vec2 unpackBoatPoint(Boat boat, int vertex) { + return unpackFloat2x16(boat.boatContour[vertex / 4][vertex % 4]); +} + +float boatDistance(Boat boat, vec2 p) { + vec2 v0 = unpackBoatPoint(boat, 0); + float d = dot(p - v0, p - v0); + float s = 1.0; + + for (int i = 0; i < BOAT_CONTOUR; i++) { + int j = (i + BOAT_CONTOUR - 1) % BOAT_CONTOUR; + vec2 vi = unpackBoatPoint(boat, i); + vec2 vj = unpackBoatPoint(boat, j); + + vec2 e = vj - vi; + vec2 w = p - vi; + vec2 b = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0); + d = min(d, dot(b, b)); + + // winding-number style inside/outside test + bvec3 c = bvec3( + p.y >= vi.y, + p.y < vj.y, + e.x * w.y > e.y * w.x + ); + if (all(c) || all(not(c))) s = -s; + } + + return (s * sqrt(d)) + BOAT_DIST_OFFSET; +} diff --git a/src/main/resources/rs117/hd/uniforms/model_data.glsl b/src/main/resources/rs117/hd/uniforms/model_data.glsl new file mode 100644 index 0000000000..b6b175712a --- /dev/null +++ b/src/main/resources/rs117/hd/uniforms/model_data.glsl @@ -0,0 +1,38 @@ +#pragma once + +#include + +#define MODEL_DATA_SIZE 7 +#define PARSER_TARGET_BUFFER modelData + +uniform isamplerBuffer modelData; + +struct ModelData { + int worldViewIdx; + int flags; + vec3 position; + int height; + float fade; // Used by Dynamic Models +}; + +BEGIN_BUFFER_PARSER(readModelData, ModelData, true) + READ_INT(worldViewIdx) + READ_INT(flags) + READ_VEC3(position) + READ_INT(height) + READ_FLOAT(fade) +END_BUFFER_PARSER() + +ModelData getModelData(int modelIdx) { + return readModelData((modelIdx - 1) * MODEL_DATA_SIZE); +} + +bool isModelStatic(in ModelData modelData) { + return (modelData.flags & 1) == 1; +} + +bool isModelDynamic(in ModelData modelData) { + return (modelData.flags & 1) == 0; +} + +#undef PARSER_TARGET_BUFFER diff --git a/src/main/resources/rs117/hd/uniforms/texture_faces.glsl b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl new file mode 100644 index 0000000000..97f68bd24f --- /dev/null +++ b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl @@ -0,0 +1,43 @@ +#pragma once + +#include + +#define PARSER_TARGET_BUFFER textureFaces + +uniform isamplerBuffer textureFaces; + +struct StaticFaceData { + ivec3 AlphaBiasHsl; + ivec3 MaterialData; + ivec3 TerrainData; +}; + +struct ModelFaceData { + ivec3 AlphaBiasHsl; + int MaterialData; +}; + +bool isFaceWindingReversed(int packedFaceData) { + return (packedFaceData & 0x80000000) != 0; +} + +bool isModelFace(int packedFaceData) { + return (packedFaceData & 0x40000000) != 0; +} + +int getFaceOffset(int packedFaceData) { + return packedFaceData & 0x3FFFFFFF; +} + +BEGIN_BUFFER_PARSER(getStaticFaceData, StaticFaceData, false) + READ_IVEC3(AlphaBiasHsl) + READ_IVEC3(MaterialData) + READ_IVEC3(TerrainData) +END_BUFFER_PARSER() + +BEGIN_BUFFER_PARSER(getModelFaceData, ModelFaceData, false) + READ_IVEC3(AlphaBiasHsl) + READ_INT(MaterialData) +END_BUFFER_PARSER() + +#undef PARSER_TARGET_BUFFER diff --git a/src/main/resources/rs117/hd/utils/constants.glsl b/src/main/resources/rs117/hd/utils/constants.glsl index 1ef390944e..c283fdb8fd 100644 --- a/src/main/resources/rs117/hd/utils/constants.glsl +++ b/src/main/resources/rs117/hd/utils/constants.glsl @@ -60,6 +60,7 @@ #include LEGACY_GREY_COLORS #include DISABLE_DIRECTIONAL_SHADING #include FLAT_SHADING +#include DITHER_FADE #include APPLY_COLOR_FILTER #include WIREFRAME #include WIND_DISPLACEMENT diff --git a/src/main/resources/rs117/hd/utils/misc.glsl b/src/main/resources/rs117/hd/utils/misc.glsl index 6e5406693b..6569597ab7 100644 --- a/src/main/resources/rs117/hd/utils/misc.glsl +++ b/src/main/resources/rs117/hd/utils/misc.glsl @@ -112,6 +112,66 @@ void undoVanillaShading(inout int hsl, vec3 unrotatedNormal) { } #endif +// 2x2 Bayer via bit permutation +// Generates ordered dithering Bayer matrix without a lookup table +float bayer2x2(vec2 pixelCoord) { + uvec2 p = uvec2(pixelCoord) & 1u; + uint v = + ((p.x & 1u) << 1u) | + ((p.y & 1u) << 0u); + + return (float(v) + 0.5) * (1.0 / 4.0); +} + +// 4x4 Bayer via bit permutation +// Generates ordered dithering Bayer matrix without a lookup table +float bayer4x4(vec2 pixelCoord) { + uvec2 p = uvec2(pixelCoord) & 3u; + uint v = + ((p.x & 1u) << 3u) | + ((p.y & 1u) << 2u) | + ((p.x & 2u) << 0u) | + ((p.y & 2u) >> 1u); + + return (float(v) + 0.5) * (1.0 / 16.0); +} + +// 8x8 Bayer via bit permutation +// Generates ordered dithering Bayer matrix without a lookup table +float bayer8x8(vec2 pixelCoord) { + uvec2 p = uvec2(pixelCoord) & 7u; + uint v = + ((p.x & 1u) << 5u) | + ((p.y & 1u) << 4u) | + ((p.x & 2u) << 2u) | + ((p.y & 2u) << 1u) | + ((p.x & 4u) >> 1u) | + ((p.y & 4u) >> 2u); + + return (float(v) + 0.5) * (1.0 / 64.0); +} + +// Based on https://www.shadertoy.com/view/4t2cRt (merger doctrine) +// Returns a dither value (0.0 or 1.0) based on coords & opacity +bool orderedDither4x4(vec2 pixelCoord, float opacity, float scaleFactor) { + float threshold = bayer4x4(pixelCoord / scaleFactor); + return threshold < clamp(opacity, 0.0, 1.0); +} + +bool orderedDither2x2(vec2 pixelCoord, float opacity, float scaleFactor) { + float threshold = bayer2x2(pixelCoord / scaleFactor); + return threshold < clamp(opacity, 0.0, 1.0); +} + +bool orderedDither8x8(vec2 pixelCoord, float opacity, float scaleFactor) { + float threshold = bayer8x8(pixelCoord / scaleFactor); + return threshold < clamp(opacity, 0.0, 1.0); +} + +float interleavedGradientNoise(vec2 p) { + return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); +} + // 2D Random float hash(in vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); @@ -189,6 +249,10 @@ vec4 saturate(vec4 value) { return clamp(value, vec4(0.0), vec4(1.0)); } +float cross2D(vec2 a, vec2 b) { + return a.x * b.y - a.y * b.x; +} + #define POISSON_DISK_LENGTH 16 vec2 getPoissonDisk(int idx) { switch(idx) { @@ -210,3 +274,57 @@ vec2 getPoissonDisk(int idx) { default: return vec2( 0.14383161, -0.14100790); } } + +bool contains(vec3 p, vec3 min, vec3 max) { + return all(greaterThanEqual(p, min)) && + all(lessThanEqual(p, max)); +} + +bool insideQuad(vec2 p, vec2 a, vec2 b, vec2 c, vec2 d) { + return cross2D(b - a, p - a) >= 0.0 && + cross2D(c - b, p - b) >= 0.0 && + cross2D(d - c, p - c) >= 0.0 && + cross2D(a - d, p - d) >= 0.0; +} + +float unpackFloat16(int val) { + uint h = uint(val) & 0xFFFFu; + + uint sign = (h & 0x8000u) << 16u; // move sign into bit 31 + uint exponent = (h >> 10u) & 0x1Fu; // 5 bits + uint mantissa = h & 0x3FFu; // 10 bits + + uint f; // resulting float32 bit pattern + + if (exponent == 0u) { + if (mantissa == 0u) { + f = sign; // signed zero + } else { + // Subnormal half -> normalize into a normal float32 + exponent = 1u; + while ((mantissa & 0x400u) == 0u) { + mantissa <<= 1u; + exponent -= 1u; + } + mantissa &= 0x3FFu; // drop the now-implicit leading bit + uint exp32 = exponent - 15u + 127u; + f = sign | (exp32 << 23u) | (mantissa << 13u); + } + } else if (exponent == 0x1Fu) { + // Inf / NaN + f = sign | 0x7F800000u | (mantissa << 13u); + } else { + // Normalized value + uint exp32 = exponent - 15u + 127u; + f = sign | (exp32 << 23u) | (mantissa << 13u); + } + + return uintBitsToFloat(f); +} + +vec2 unpackFloat2x16(int v) { + uint bits = uint(v); + uint lo = bits & 0xFFFFu; // x half, low 16 bits + uint hi = (bits >> 16u) & 0xFFFFu; // z half, high 16 bits + return vec2(unpackFloat16(int(lo)), unpackFloat16(int(hi))); +} diff --git a/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl b/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl new file mode 100644 index 0000000000..09d24618fd --- /dev/null +++ b/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl @@ -0,0 +1,217 @@ +#pragma once + +// Number of scalar components per fetched texel. +// Valid range: 1-4. +#include TEXEL_SIZE +#ifndef TEXEL_SIZE + #define TEXEL_SIZE 4 +#endif + +#if TEXEL_SIZE < 1 || TEXEL_SIZE > 4 + #error TEXEL_SIZE must be between 1 and 4 +#endif + +#ifdef GL_KHR_shader_subgroup_vote + #extension GL_KHR_shader_subgroup_vote : enable + #extension GL_KHR_shader_subgroup_ballot : enable +#endif + +// Sequential reader for tightly-packed scalar data stored in a buffer texture. +// +// Layout assumptions: +// - Data is packed scalar-by-scalar with NO padding. +// - Floats are stored as IEEE-754 bit patterns inside integer components. +// - TEXEL_SIZE defines how many usable components exist per fetched texel. +// +// Example packed stream: +// [int][float][vec3][ivec2]... +// +// This reader caches the currently loaded texel to avoid redundant texelFetch +// calls during sequential access. +// +// If GL_KHR_shader_subgroup_vote is supported & then data reads can be scalarized +// when the starting position is the same across all invoked lanes + +struct TexBufferReader { + // Cached texel data. + // Always ivec4 regardless of TEXEL_SIZE. + ivec4 data; + + // Current scalar position in stream. + int position; + + // Currently cached texel index. + int loadedTexel; + +#ifdef GL_KHR_shader_subgroup_vote + // if true texel fetches are scalarized via subgroup ops + bool scalar; + + // if true then this lene has been elected to peform reads + bool elected; +#endif +}; + +TexBufferReader buildTexBufferReader(int position, bool scalar) { + TexBufferReader reader; + reader.position = position; + reader.data = ivec4(0); + reader.loadedTexel = -1; + +#ifdef GL_KHR_shader_subgroup_vote + reader.scalar = scalar && subgroupAllEqual(position); + reader.elected = subgroupElect(); +#else + reader.scalar = false; + reader.elected = false; +#endif + + return reader; +} + +int readInt(isamplerBuffer buf, inout TexBufferReader reader) { +#if TEXEL_SIZE == 4 + int texelIndex = reader.position >> 2; + int component = reader.position & 3; +#else + int texelIndex = reader.position / TEXEL_SIZE; + int component = reader.position % TEXEL_SIZE; +#endif + + if (texelIndex != reader.loadedTexel) { +#ifdef GL_KHR_shader_subgroup_vote + if (reader.scalar) { + ivec4 fetched; + if (reader.elected) + fetched = texelFetch(buf, texelIndex); + reader.data = subgroupBroadcastFirst(fetched); + } else +#endif + { + reader.data = texelFetch(buf, texelIndex); + } + reader.loadedTexel = texelIndex; + } + + reader.position++; + + switch (component) { + case 0: return reader.data.x; + case 1: return reader.data.y; + case 2: return reader.data.z; + default: return reader.data.w; + } +} + +uint readUInt(isamplerBuffer buf, inout TexBufferReader reader) { + return uint(readInt(buf, reader)); +} + +float readFloat(isamplerBuffer buf, inout TexBufferReader reader) { + return intBitsToFloat(readInt(buf, reader)); +} + +bool readBool(isamplerBuffer buf, inout TexBufferReader reader) { + return readInt(buf, reader) != 0; +} + +ivec2 readIVec2(isamplerBuffer buf, inout TexBufferReader reader) { + return ivec2( + readInt(buf, reader), + readInt(buf, reader) + ); +} + +ivec3 readIVec3(isamplerBuffer buf, inout TexBufferReader reader) { + return ivec3( + readInt(buf, reader), + readInt(buf, reader), + readInt(buf, reader) + ); +} + +ivec4 readIVec4(isamplerBuffer buf, inout TexBufferReader reader) { + return ivec4( + readInt(buf, reader), + readInt(buf, reader), + readInt(buf, reader), + readInt(buf, reader) + ); +} + +uvec2 readUVec2(isamplerBuffer buf, inout TexBufferReader reader) { + return uvec2( + readUInt(buf, reader), + readUInt(buf, reader) + ); +} + +uvec3 readUVec3(isamplerBuffer buf, inout TexBufferReader reader) { + return uvec3( + readUInt(buf, reader), + readUInt(buf, reader), + readUInt(buf, reader) + ); +} + +uvec4 readUVec4(isamplerBuffer buf, inout TexBufferReader reader) { + return uvec4( + readUInt(buf, reader), + readUInt(buf, reader), + readUInt(buf, reader), + readUInt(buf, reader) + ); +} + +vec2 readVec2(isamplerBuffer buf, inout TexBufferReader reader) { + return vec2( + readFloat(buf, reader), + readFloat(buf, reader) + ); +} + +vec3 readVec3(isamplerBuffer buf, inout TexBufferReader reader) { + return vec3( + readFloat(buf, reader), + readFloat(buf, reader), + readFloat(buf, reader) + ); +} + +vec4 readVec4(isamplerBuffer buf, inout TexBufferReader reader) { + return vec4( + readFloat(buf, reader), + readFloat(buf, reader), + readFloat(buf, reader), + readFloat(buf, reader) + ); +} + +void skipScalars(inout TexBufferReader reader, int count) { + reader.position += count; +} + +void rewindReader(inout TexBufferReader reader, int position) { + reader.position = position; +} + +#define BEGIN_BUFFER_PARSER(FuncName, StructType, Scalar) \ +StructType FuncName(int offset) { \ + TexBufferReader reader = \ + buildTexBufferReader(offset, Scalar); \ + \ + StructType data; + +#define END_BUFFER_PARSER() \ + return data; \ +} + +#define READ_INT(field) data.field = readInt(PARSER_TARGET_BUFFER, reader); +#define READ_FLOAT(field) data.field = readFloat(PARSER_TARGET_BUFFER, reader); +#define READ_BOOL(field) data.field = readBool(PARSER_TARGET_BUFFER, reader); +#define READ_IVEC2(field) data.field = readIVec2(PARSER_TARGET_BUFFER, reader); +#define READ_IVEC3(field) data.field = readIVec3(PARSER_TARGET_BUFFER, reader); +#define READ_IVEC4(field) data.field = readIVec4(PARSER_TARGET_BUFFER, reader); +#define READ_VEC2(field) data.field = readVec2(PARSER_TARGET_BUFFER, reader); +#define READ_VEC3(field) data.field = readVec3(PARSER_TARGET_BUFFER, reader); +#define READ_VEC4(field) data.field = readVec4(PARSER_TARGET_BUFFER, reader); diff --git a/src/main/resources/rs117/hd/utils/water.glsl b/src/main/resources/rs117/hd/utils/water.glsl index bfb9a62f53..9c39e77bc0 100644 --- a/src/main/resources/rs117/hd/utils/water.glsl +++ b/src/main/resources/rs117/hd/utils/water.glsl @@ -131,9 +131,25 @@ vec4 sampleWater(int waterTypeIndex, vec3 viewDir) { baseColor = mix(baseColor, surfaceColor, waterType.fresnelAmount); if (waterType.fresnelAmount == 0.85) baseColor *= .75f; // Sailing hack + + float boatFoamMask = 0.0; + #if ZONE_RENDERER + // TODO: This could be done in the vertex shader to avoid checking per fragment + const float boatFoamSize = 128.0; + for (int i = 0; i < boatCount; i++) { + float dist = boatDistance(boats[i], IN.position.xz); + if (dist < boatFoamSize) { + boatFoamMask += 1.0 - (dist / boatFoamSize); + if (boatFoamMask >= 1.0) + break; + } + } + #endif + float shoreLineMask = 1 - dot(IN.texBlend, (fAlphaBiasHsl & 127) / 127.f); float maxFoamAmount = 0.8; - float foamAmount = min(shoreLineMask, maxFoamAmount); + float foamAmount = max(shoreLineMask, boatFoamMask); + foamAmount = min(foamAmount, maxFoamAmount); float foamDistance = 0.7; vec3 foamColor = waterType.foamColor; foamColor = foamColor * foamMask * compositeLight; diff --git a/src/main/resources/rs117/hd/utils/wind_character_displacement.glsl b/src/main/resources/rs117/hd/utils/wind_character_displacement.glsl new file mode 100644 index 0000000000..e30c09419e --- /dev/null +++ b/src/main/resources/rs117/hd/utils/wind_character_displacement.glsl @@ -0,0 +1,175 @@ +#pragma once + +#include + +#include +#include + +#define BOAT_DISPLACEMENT_MARGIN 128.0 + +// Needs to match Ordinal Values of WindDisplacement.Java +#define WIND_DISPLACEMENT_DISABLED 0 +#define WIND_DISPLACEMENT_OBJECT 1 +#define WIND_DISPLACEMENT_OBJECT_NO_GROUND_DISPLACEMENT 2 +#define WIND_DISPLACEMENT_VERTEX 3 +#define WIND_DISPLACEMENT_VERTEX_WITH_HEMISPHERE_BLEND 4 +#define WIND_DISPLACEMENT_VERTEX_JIGGLE 5 + +struct ObjectWindSample { + vec3 direction; + vec3 displacement; + float heightBasedStrength; +}; + +ObjectWindSample computeWindSample(vec3 modelPos, int height) { + ObjectWindSample windSample; + windSample.direction = vec3(0); + windSample.displacement = vec3(0); + windSample.heightBasedStrength = 0.0; + +#if WIND_DISPLACEMENT + float modelNoise = noise((modelPos.xz + vec2(windOffset)) * WIND_DISPLACEMENT_NOISE_RESOLUTION); + float angle = modelNoise * (PI / 2.0); + float c = cos(angle); + float s = sin(angle); + + windSample.direction = normalize(vec3(windDirectionX * c + windDirectionZ * s, 0.0, -windDirectionX * s + windDirectionZ * c)); + windSample.heightBasedStrength = saturate((abs(modelPos.y) + float(height)) / windCeiling) * windStrength; + windSample.displacement = windSample.direction.xyz * (windSample.heightBasedStrength * modelNoise); +#endif + + return windSample; +} + +float getModelWindDisplacementMod(int vertexFlags) { + const float modifiers[7] = float[7](0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0); + int modifierIDx = (vertexFlags >> MATERIAL_FLAG_WIND_MODIFIER) & 0x7; + return modifiers[modifierIDx]; +} + +vec3 applyCharacterDisplacement(vec4 characterData, vec2 vertPos, float height, float strength, inout float offsetAccum) { + vec2 offset = vertPos - characterData.xy; + float offsetLen = length(offset); + + if (offsetLen >= characterData.z) + return vec3(0); + + float offsetFrac = saturate(1.0 - (offsetLen / characterData.z)); + float displacementFrac = offsetFrac * offsetFrac * characterData.w; + + vec3 horizontalDisplacement = normalize(vec3(offset.x, 0.0, offset.y)) * (height * strength * displacementFrac * 0.5); + vec3 verticalDisplacement = vec3(0.0, height * strength * displacementFrac, 0.0); + + offsetAccum += offsetFrac; + + return mix(horizontalDisplacement, verticalDisplacement, offsetFrac); +} + +#if ZONE_RENDERER +vec3 applyBoatDisplacement(int boatIdx, vec2 vertPos, float height, float strength, inout float offsetAccum) { + float d = boatDistance(boats[boatIdx], vertPos); + + float falloffRadius = BOAT_DISPLACEMENT_MARGIN; + if (d >= falloffRadius) + return vec3(0.0); + + float offsetFrac = saturate(1.0 - (d / falloffRadius)); + float displacementFrac = offsetFrac * offsetFrac; + + offsetAccum += offsetFrac; + + return vec3(0.0, height * strength * displacementFrac, 0.0); +} +#endif + +vec3 applyWindDisplacementVertex( + const ObjectWindSample windSample, + int vertexFlags, + float modelHeight, + vec3 worldVertPos, + vec3 localVertPos, + vec3 normal +) { + vec3 displacement = vec3(0); + + int windDisplacementMode = (vertexFlags >> MATERIAL_FLAG_WIND_SWAYING) & 0x7; + if (windDisplacementMode <= WIND_DISPLACEMENT_DISABLED) + return displacement; + + float strength = saturate(abs(localVertPos.y) / modelHeight); + if ((vertexFlags >> MATERIAL_FLAG_INVERT_DISPLACEMENT_STRENGTH & 1) == 1) + strength = 1.0 - strength; + + float modelDisplacementMod = getModelWindDisplacementMod(vertexFlags); + +#if WIND_DISPLACEMENT + if (windDisplacementMode >= WIND_DISPLACEMENT_VERTEX) { + const float VertexSnapping = 150.0; + const float VertexDisplacementMod = 0.2; + float windNoise = mix(-0.5, 0.5, noise((snap(localVertPos, VertexSnapping).xz + vec2(windOffset)) * WIND_DISPLACEMENT_NOISE_RESOLUTION)); + + if (windDisplacementMode == WIND_DISPLACEMENT_VERTEX_WITH_HEMISPHERE_BLEND) { + const float minDist = 50; + const float blendDist = 10.0; + + float distBlend = saturate(((abs(localVertPos.x) + abs(localVertPos.z)) - minDist) / blendDist); + float heightFade = saturate((strength - 0.5) / 0.2); + + strength *= mix(0.0, mix(distBlend, 1.0, heightFade), step(0.3, strength)); + } else { + if (windDisplacementMode == WIND_DISPLACEMENT_VERTEX_JIGGLE) { + vec3 vertSkew = safe_normalize(cross(normal, vec3(0, 1, 0))); + + strength *= modelDisplacementMod; + displacement = ((windNoise * (windSample.heightBasedStrength * strength) * 0.5) * vertSkew); + + vertSkew = safe_normalize(cross(normal, vec3(1, 0, 0))); + + displacement += (((1.0 - windNoise) * (windSample.heightBasedStrength * strength) * 0.5) * vertSkew); + } else { + displacement = ((windNoise * (windSample.heightBasedStrength * strength * VertexDisplacementMod)) * windSample.direction); + + strength = saturate(strength - VertexDisplacementMod) * modelDisplacementMod; + } + } + } + + if (windDisplacementMode != WIND_DISPLACEMENT_VERTEX_JIGGLE) { + displacement += windSample.displacement * strength * modelDisplacementMod; + } +#endif + + if (windDisplacementMode == WIND_DISPLACEMENT_OBJECT) { + vec2 worldVert = worldVertPos.xz; + float fractAccum = 0.0; +#if CHARACTER_DISPLACEMENT + for (int i = 0; i < characterPositionCount; i++) { + displacement += applyCharacterDisplacement(characterPositions[i], worldVert, modelHeight, strength, fractAccum); + if (fractAccum >= 1.0) + break; + } + +#if ZONE_RENDERER + if (fractAccum < 1.0) { + for (int i = 0; i < boatCount; i++) { + displacement += applyBoatDisplacement(i, worldVert, modelHeight, 1.25, fractAccum); + if (fractAccum >= 1.0) + break; + } + } +#endif +#endif + } + + return displacement; +} + +void applyWindDisplacementFace(const ObjectWindSample windSample, int vertexFlags, float modelHeight, vec3 modelPos, + in vec3 vertA, in vec3 vertB, in vec3 vertC, + in vec3 normA, in vec3 normB, in vec3 normC, + inout vec3 displacementA, inout vec3 displacementB, inout vec3 displacementC +) { + displacementA += applyWindDisplacementVertex(windSample, vertexFlags, modelHeight, modelPos + vertA, vertA, normA); + displacementB += applyWindDisplacementVertex(windSample, vertexFlags, modelHeight, modelPos + vertB, vertB, normB); + displacementC += applyWindDisplacementVertex(windSample, vertexFlags, modelHeight, modelPos + vertC, vertC, normC); +}