diff --git a/build.gradle b/build.gradle index 001bd87a6f..ef49747321 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ dependencies { } group = 'rs117.hd' -version = '1.3.1' +version = '1.3.2' sourceCompatibility = '11' tasks.withType(JavaCompile).configureEach { diff --git a/schemas/environments.schema.json b/schemas/environments.schema.json index 0359a7f573..2f630373e7 100644 --- a/schemas/environments.schema.json +++ b/schemas/environments.schema.json @@ -17,15 +17,19 @@ }, "isUnderwater": { "type": "boolean", - "description": "." + "description": "Whether the area is an underwater area, such as Guardians of the Rift." }, "allowSkyOverride": { "type": "boolean", - "description": "." + "description": "Whether to allow overriding the sky with default sky." }, "lightningEffects": { "type": "boolean", - "description": "." + "description": "Whether the area should have flashing lightning effects." + }, + "instantTransition": { + "type": "boolean", + "description": "Whether transitions to and from this area should always be instant." }, "ambientColor": { "type": [ "string", "array" ], diff --git a/schemas/lights.schema.json b/schemas/lights.schema.json index 7795cb8d47..00730722e9 100644 --- a/schemas/lights.schema.json +++ b/schemas/lights.schema.json @@ -32,6 +32,7 @@ "alignment": { "type": "string", "enum": [ + "CUSTOM", "CENTER", "NORTH", @@ -62,6 +63,15 @@ ], "description": "Where to place the light in relation to what it's attached to, be it an NPC, object or world location." }, + "offset": { + "type": "array", + "description": "The light's position relative to the center of the model it's attached to, before rotation. Only applies if alignment is set to CUSTOM, which is the default.", + "minItems": 3, + "maxItems": 3, + "items": { + "type": "integer" + } + }, "height": { "type": "integer", "description": "The height in scene units (128 per tile) to place the light above the current tile or entity base height." @@ -107,7 +117,7 @@ }, "fadeOverlap": { "type": "boolean", - "description": "If enabled, fade-in and fade-out can overlap multiplicatively if spaced close enough together." + "description": "If enabled, fade-in and fade-out can overlap if spaced close enough together." }, "spawnDelay": { "type": "number", diff --git a/schemas/model_overrides.schema.json b/schemas/model_overrides.schema.json index 03e0735271..b862a8f638 100644 --- a/schemas/model_overrides.schema.json +++ b/schemas/model_overrides.schema.json @@ -125,6 +125,10 @@ "type": "boolean", "description": "Whether flat normals should be used instead of smooth normals. Defaults to false." }, + "upwardsNormals": { + "type": "boolean", + "description": "Whether normals should always point directly up. Defaults to false." + }, "hideVanillaShadows": { "type": "boolean", "description": "Whether the model has fake shadows which should be removed. Defaults to false." diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index aab624916a..e5688aa521 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -43,7 +43,6 @@ import java.nio.ShortBuffer; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -84,6 +83,7 @@ import rs117.hd.config.ShadowMode; import rs117.hd.config.UIScalingMode; import rs117.hd.config.VanillaShadowMode; +import rs117.hd.config.WaterTransparencyType; import rs117.hd.data.WaterType; import rs117.hd.data.environments.Area; import rs117.hd.data.materials.Material; @@ -98,6 +98,7 @@ import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; import rs117.hd.scene.EnvironmentManager; +import rs117.hd.scene.FishingSpotReplacer; import rs117.hd.scene.LightManager; import rs117.hd.scene.ModelOverrideManager; import rs117.hd.scene.ProceduralGenerator; @@ -120,6 +121,8 @@ import rs117.hd.utils.buffer.GLBuffer; import rs117.hd.utils.buffer.GpuIntBuffer; +import static net.runelite.api.Constants.SCENE_SIZE; +import static net.runelite.api.Constants.*; import static net.runelite.api.Perspective.*; import static org.lwjgl.opencl.CL10.*; import static org.lwjgl.opengl.GL43C.*; @@ -157,7 +160,7 @@ public class HdPlugin extends Plugin implements DrawCallbacks { public static final float NEAR_PLANE = 1; public static final int MAX_FACE_COUNT = 6144; - public static final int MAX_DISTANCE = Constants.EXTENDED_SCENE_SIZE; + public static final int MAX_DISTANCE = EXTENDED_SCENE_SIZE; public static final int GROUND_MIN_Y = 350; // how far below the ground models extend public static final int MAX_FOG_DEPTH = 100; public static final int SCALAR_BYTES = 4; @@ -214,6 +217,9 @@ public class HdPlugin extends Plugin implements DrawCallbacks { @Inject private ModelHasher modelHasher; + @Inject + private FishingSpotReplacer fishingSpotReplacer; + @Inject private DeveloperTools developerTools; @@ -291,6 +297,7 @@ public class HdPlugin extends Plugin implements DrawCallbacks { private int texWaterReflection = -1; private int texWaterReflectionDepthMap = -1; + private boolean enableWaterFoam; private int texTileHeightMap; private final GLBuffer hStagingBufferVertices = new GLBuffer(); // temporary scene vertex buffer @@ -338,6 +345,11 @@ public class HdPlugin extends Plugin implements DrawCallbacks { private float lastPlanarReflectionResolution; private boolean lastLinearAlphaBlending; + private int waterCausticsStrengthConfig; + private int waterWaveSizeConfig; + private int waterWaveSpeedConfig; + private int waterFoamAmountConfig; + private int viewportOffsetX; private int viewportOffsetY; @@ -353,6 +365,8 @@ public class HdPlugin extends Plugin implements DrawCallbacks { private int uniWaterColorLight; private int uniWaterColorMid; private int uniWaterColorDark; + private int uniWaterTransparencyType; + private int uniWaterTransparencyConfig; private int uniAmbientStrength; private int uniAmbientColor; private int uniLightStrength; @@ -370,11 +384,16 @@ public class HdPlugin extends Plugin implements DrawCallbacks { private int uniShadowsEnabled; private int uniUnderwaterEnvironment; private int uniUnderwaterCaustics; + private int uniShorelineCaustics; private int uniUnderwaterCausticsColor; private int uniUnderwaterCausticsStrength; private int uniWaterHeight; private int uniWaterReflectionEnabled; private int uniCameraPos; + private int uniWaterCausticsStrengthConfig; + private int uniWaterWaveSizeConfig; + private int uniWaterWaveSpeedConfig; + private int uniWaterFoamAmountConfig; // Shadow program uniforms private int uniShadowLightProjectionMatrix; @@ -406,6 +425,8 @@ public class HdPlugin extends Plugin implements DrawCallbacks { public boolean configTzhaarHD; public boolean configProjectileLights; public boolean configNpcLights; + public WaterTransparencyType waterTransparencyType; + public int waterTransparencyConfig; public boolean configHideFakeShadows; public boolean configLegacyGreyColors; public boolean configModelBatching; @@ -424,6 +445,7 @@ public class HdPlugin extends Plugin implements DrawCallbacks { public boolean useLowMemoryMode; public boolean enableDetailedTimers; public boolean enableShadowMapOverlay; + public boolean enableFreezeFrame; @Getter private boolean isActive; @@ -478,7 +500,6 @@ protected void startUp() { elapsedClientTime = 0; lastFrameTimeMillis = 0; lastFrameClientTime = 0; - visibleLightCount = 0; AWTContext.loadNatives(); canvas = client.getCanvas(); @@ -603,6 +624,7 @@ protected void startUp() { modelPusher.startUp(); lightManager.startUp(); environmentManager.startUp(); + fishingSpotReplacer.startUp(); isActive = true; hasLoggedIn = client.getGameState().getState() > GameState.LOGGING_IN.getState(); @@ -650,6 +672,7 @@ protected void shutDown() { modelOverrideManager.shutDown(); lightManager.shutDown(); environmentManager.shutDown(); + fishingSpotReplacer.shutDown(); if (lwjglInitialized) { lwjglInitialized = false; @@ -723,6 +746,14 @@ public void restartPlugin() { })); } + public void toggleFreezeFrame() { + clientThread.invoke(() -> { + enableFreezeFrame = !enableFreezeFrame; + if (enableFreezeFrame) + redrawPreviousFrame = true; + }); + } + private String generateFetchCases(String array, int from, int to) { int length = to - from; @@ -805,6 +836,8 @@ private void initPrograms() throws ShaderException, IOException .define("SHADOW_MAP_OVERLAY", enableShadowMapOverlay) .define("LINEAR_ALPHA_BLENDING", configLinearAlphaBlending) .define("PLANAR_REFLECTION_RESOLUTION", config.reflectionResolution() / 100f) + .define("WATER_FOAM", config.enableWaterFoam()) + .define("WATER_LIGHT_SCATTERING", config.waterLightScattering()) .addIncludePath(SHADER_PATH); glSceneProgram = PROGRAM.compile(template); @@ -876,6 +909,8 @@ private void initUniforms() { uniWaterColorMid = glGetUniformLocation(glSceneProgram, "waterColorMid"); uniWaterColorDark = glGetUniformLocation(glSceneProgram, "waterColorDark"); uniDrawDistance = glGetUniformLocation(glSceneProgram, "drawDistance"); + uniWaterTransparencyConfig = glGetUniformLocation(glSceneProgram, "waterTransparencyConfig"); + uniWaterTransparencyType = glGetUniformLocation(glSceneProgram, "waterTransparencyType"); uniExpandedMapLoadingChunks = glGetUniformLocation(glSceneProgram, "expandedMapLoadingChunks"); uniAmbientStrength = glGetUniformLocation(glSceneProgram, "ambientStrength"); uniAmbientColor = glGetUniformLocation(glSceneProgram, "ambientColor"); @@ -894,10 +929,15 @@ private void initUniforms() { uniShadowsEnabled = glGetUniformLocation(glSceneProgram, "shadowsEnabled"); uniUnderwaterEnvironment = glGetUniformLocation(glSceneProgram, "underwaterEnvironment"); uniUnderwaterCaustics = glGetUniformLocation(glSceneProgram, "underwaterCaustics"); + uniShorelineCaustics = glGetUniformLocation(glSceneProgram, "shorelineCaustics"); uniUnderwaterCausticsColor = glGetUniformLocation(glSceneProgram, "underwaterCausticsColor"); uniUnderwaterCausticsStrength = glGetUniformLocation(glSceneProgram, "underwaterCausticsStrength"); uniWaterHeight = glGetUniformLocation(glSceneProgram, "waterHeight"); uniWaterReflectionEnabled = glGetUniformLocation(glSceneProgram, "waterReflectionEnabled"); + uniWaterCausticsStrengthConfig = glGetUniformLocation(glSceneProgram, "waterCausticsStrengthConfig"); + uniWaterWaveSizeConfig = glGetUniformLocation(glSceneProgram, "waterWaveSizeConfig"); + uniWaterWaveSpeedConfig = glGetUniformLocation(glSceneProgram, "waterWaveSpeedConfig"); + uniWaterFoamAmountConfig = glGetUniformLocation(glSceneProgram, "waterFoamAmountConfig"); uniCameraPos = glGetUniformLocation(glSceneProgram, "cameraPos"); uniTextureArray = glGetUniformLocation(glSceneProgram, "textureArray"); uniElapsedTime = glGetUniformLocation(glSceneProgram, "elapsedTime"); @@ -1361,7 +1401,7 @@ private void destroyShadowMapFbo() { } private void initTileHeightMap(Scene scene) { - final int TILE_HEIGHT_BUFFER_SIZE = Constants.MAX_Z * Constants.EXTENDED_SCENE_SIZE * Constants.EXTENDED_SCENE_SIZE * Short.BYTES; + final int TILE_HEIGHT_BUFFER_SIZE = Constants.MAX_Z * EXTENDED_SCENE_SIZE * EXTENDED_SCENE_SIZE * Short.BYTES; ShortBuffer tileBuffer = ByteBuffer .allocateDirect(TILE_HEIGHT_BUFFER_SIZE) .order(ByteOrder.nativeOrder()) @@ -1369,8 +1409,8 @@ private void initTileHeightMap(Scene scene) { int[][][] tileHeights = scene.getTileHeights(); for (int z = 0; z < Constants.MAX_Z; ++z) { - for (int y = 0; y < Constants.EXTENDED_SCENE_SIZE; ++y) { - for (int x = 0; x < Constants.EXTENDED_SCENE_SIZE; ++x) { + for (int y = 0; y < EXTENDED_SCENE_SIZE; ++y) { + for (int x = 0; x < EXTENDED_SCENE_SIZE; ++x) { int h = tileHeights[z][x][y]; assert (h & 0b111) == 0; h >>= 3; @@ -1389,7 +1429,7 @@ private void initTileHeightMap(Scene scene) { glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage3D(GL_TEXTURE_3D, 0, GL_R16I, - Constants.EXTENDED_SCENE_SIZE, Constants.EXTENDED_SCENE_SIZE, Constants.MAX_Z, + EXTENDED_SCENE_SIZE, EXTENDED_SCENE_SIZE, Constants.MAX_Z, 0, GL_RED_INTEGER, GL_SHORT, tileBuffer ); @@ -1465,6 +1505,7 @@ public void drawScene(double cameraX, double cameraY, double cameraZ, double cam if (sceneContext == null) return; + frameTimer.begin(Timer.DRAW_FRAME); frameTimer.begin(Timer.DRAW_SCENE); final Scene scene = client.getScene(); @@ -1473,72 +1514,79 @@ public void drawScene(double cameraX, double cameraY, double cameraZ, double cam viewportOffsetX = client.getViewportXOffset(); viewportOffsetY = client.getViewportYOffset(); - if (!redrawPreviousFrame) { - // Only reset the target buffer offset right before drawing the scene. That way if there are frames - // after this that don't involve a scene draw, like during LOADING/HOPPING/CONNECTION_LOST, we can - // still redraw the previous frame's scene to emulate the client behavior of not painting over the - // viewport buffer. - renderBufferOffset = sceneContext.staticVertexCount; - - // Push unordered models that should always be drawn at the start of each frame. - // Used to fix issues like the right-click menu causing underwater tiles to disappear. - var staticUnordered = sceneContext.staticUnorderedModelBuffer.getBuffer(); - modelPassthroughBuffer - .ensureCapacity(staticUnordered.limit()) - .put(staticUnordered); - staticUnordered.rewind(); - numPassthroughModels += staticUnordered.limit() / 8; - } - - cameraPosition[0] = (float) cameraX; - cameraPosition[1] = (float) cameraY; - cameraPosition[2] = (float) cameraZ; - cameraOrientation[0] = (float) cameraYaw; - cameraOrientation[1] = (float) cameraPitch; + if (!enableFreezeFrame) { + if (!redrawPreviousFrame) { + // Only reset the target buffer offset right before drawing the scene. That way if there are frames + // after this that don't involve a scene draw, like during LOADING/HOPPING/CONNECTION_LOST, we can + // still redraw the previous frame's scene to emulate the client behavior of not painting over the + // viewport buffer. + renderBufferOffset = sceneContext.staticVertexCount; + + // Push unordered models that should always be drawn at the start of each frame. + // Used to fix issues like the right-click menu causing underwater tiles to disappear. + var staticUnordered = sceneContext.staticUnorderedModelBuffer.getBuffer(); + modelPassthroughBuffer + .ensureCapacity(staticUnordered.limit()) + .put(staticUnordered); + staticUnordered.rewind(); + numPassthroughModels += staticUnordered.limit() / 8; + } - if (sceneContext.scene == scene) { - cameraFocalPoint[0] = client.getOculusOrbFocalPointX(); - cameraFocalPoint[1] = client.getOculusOrbFocalPointY(); - Arrays.fill(cameraShift, 0); + cameraPosition[0] = (float) cameraX; + cameraPosition[1] = (float) cameraY; + cameraPosition[2] = (float) cameraZ; + cameraOrientation[0] = (float) cameraYaw; + cameraOrientation[1] = (float) cameraPitch; - try { - environmentManager.update(sceneContext); - lightManager.update(sceneContext); - } catch (Exception ex) { - log.error("Error while updating environment or lights:", ex); - stopPlugin(); - return; + if (sceneContext.scene == scene) { + cameraFocalPoint[0] = client.getOculusOrbFocalPointX(); + cameraFocalPoint[1] = client.getOculusOrbFocalPointY(); + Arrays.fill(cameraShift, 0); + + try { + frameTimer.begin(Timer.UPDATE_ENVIRONMENT); + environmentManager.update(sceneContext); + frameTimer.end(Timer.UPDATE_ENVIRONMENT); + + frameTimer.begin(Timer.UPDATE_LIGHTS); + lightManager.update(sceneContext); + frameTimer.end(Timer.UPDATE_LIGHTS); + } catch (Exception ex) { + log.error("Error while updating environment or lights:", ex); + stopPlugin(); + return; + } + } else { + cameraShift[0] = cameraFocalPoint[0] - client.getOculusOrbFocalPointX(); + cameraShift[1] = cameraFocalPoint[1] - client.getOculusOrbFocalPointY(); + cameraPosition[0] += cameraShift[0]; + cameraPosition[2] += cameraShift[1]; } - } else { - cameraShift[0] = cameraFocalPoint[0] - client.getOculusOrbFocalPointX(); - cameraShift[1] = cameraFocalPoint[1] - client.getOculusOrbFocalPointY(); - cameraPosition[0] += cameraShift[0]; - cameraPosition[2] += cameraShift[1]; - } - - uniformBufferCamera - .clear() - .putFloat(cameraOrientation[0]) - .putFloat(cameraOrientation[1]) - .putInt(client.getCenterX()) - .putInt(client.getCenterY()) - .putInt(client.getScale()) - .putFloat(cameraPosition[0]) - .putFloat(cameraPosition[1]) - .putFloat(cameraPosition[2]) - .flip(); - glBindBuffer(GL_UNIFORM_BUFFER, hUniformBufferCamera.glBufferId); - glBufferSubData(GL_UNIFORM_BUFFER, 0, uniformBufferCamera); + + uniformBufferCamera + .clear() + .putFloat(cameraOrientation[0]) + .putFloat(cameraOrientation[1]) + .putInt(client.getCenterX()) + .putInt(client.getCenterY()) + .putInt(client.getScale()) + .putFloat(cameraPosition[0]) + .putFloat(cameraPosition[1]) + .putFloat(cameraPosition[2]) + .flip(); + glBindBuffer(GL_UNIFORM_BUFFER, hUniformBufferCamera.glBufferId); + glBufferSubData(GL_UNIFORM_BUFFER, 0, uniformBufferCamera); + } if (sceneContext.scene == scene) { // Update lights UBO uniformBufferLights.clear(); - ArrayList visibleLights = lightManager.getVisibleLights(configMaxDynamicLights); - visibleLightCount = visibleLights.size(); - for (Light light : visibleLights) { - uniformBufferLights.putFloat(light.x + cameraShift[0]); - uniformBufferLights.putFloat(light.z); - uniformBufferLights.putFloat(light.y + cameraShift[1]); + assert sceneContext.numVisibleLights <= configMaxDynamicLights; + for (int i = 0; i < sceneContext.numVisibleLights; i++) { + Light light = sceneContext.lights.get(i); + uniformBufferLights.putFloat(light.pos[0] + cameraShift[0]); + uniformBufferLights.putFloat(light.pos[1]); + uniformBufferLights.putFloat(light.pos[2] + cameraShift[1]); uniformBufferLights.putFloat(light.radius * light.radius); uniformBufferLights.putFloat(light.color[0] * light.strength); uniformBufferLights.putFloat(light.color[1] * light.strength); @@ -1825,6 +1873,7 @@ private void prepareInterfaceTexture(int canvasWidth, int canvasHeight) { public void draw(int overlayColor) { final GameState gameState = client.getGameState(); if (gameState == GameState.STARTING) { + frameTimer.end(Timer.DRAW_FRAME); return; } @@ -2080,7 +2129,8 @@ public void draw(int overlayColor) { glUniform3fv(uniWaterColorLight, waterColorLight); glUniform3fv(uniWaterColorMid, waterColorMid); glUniform3fv(uniWaterColorDark, waterColorDark); - + glUniform1f(uniWaterTransparencyType, waterTransparencyType.ordinal()); + glUniform1f(uniWaterTransparencyConfig, waterTransparencyConfig); float brightness = config.brightness() / 20f; glUniform1f(uniAmbientStrength, environmentManager.currentAmbientStrength * brightness); glUniform3fv(uniAmbientColor, environmentManager.currentAmbientColor); @@ -2095,17 +2145,23 @@ public void draw(int overlayColor) { glUniform1f(uniGroundFogOpacity, config.groundFog() ? environmentManager.currentGroundFogOpacity : 0); // Lights & lightning - glUniform1i(uniPointLightsCount, visibleLightCount); + glUniform1i(uniPointLightsCount, sceneContext.numVisibleLights); glUniform1f(uniLightningBrightness, environmentManager.getLightningBrightness()); glUniform1f(uniSaturation, config.saturation() / 100f); glUniform1f(uniContrast, config.contrast() / 100f); glUniform1i(uniUnderwaterEnvironment, environmentManager.isUnderwater() ? 1 : 0); glUniform1i(uniUnderwaterCaustics, config.underwaterCaustics() ? 1 : 0); + glUniform1i(uniShorelineCaustics, config.shorelineCaustics() ? 1 : 0); glUniform3fv(uniUnderwaterCausticsColor, environmentManager.currentUnderwaterCausticsColor); glUniform1f(uniUnderwaterCausticsStrength, environmentManager.currentUnderwaterCausticsStrength); glUniform1f(uniElapsedTime, elapsedTime); + glUniform1i(uniWaterCausticsStrengthConfig, config.waterCausticsStrengthConfig()); + glUniform1i(uniWaterWaveSizeConfig, config.waterWaveSizeConfig()); + glUniform1i(uniWaterWaveSpeedConfig, config.waterWaveSpeedConfig()); + glUniform1i(uniWaterFoamAmountConfig, config.waterFoamAmountConfig()); + // Extract the 3rd column from the light view matrix (the float array is column-major) // This produces the light's forward direction vector in world space glUniform3f(uniLightDir, lightViewMatrix[2], lightViewMatrix[6], lightViewMatrix[10]); @@ -2304,6 +2360,7 @@ public void draw(int overlayColor) { glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + frameTimer.end(Timer.DRAW_FRAME); frameTimer.endFrameAndReset(); frameModelInfoMap.clear(); checkGLErrors(); @@ -2504,6 +2561,7 @@ public synchronized void swapScene(Scene scene) { } lightManager.loadSceneLights(nextSceneContext, sceneContext); + fishingSpotReplacer.despawnRuneLiteObjects(); if (sceneContext != null) sceneContext.destroy(); @@ -2590,6 +2648,8 @@ private void updateCachedConfigs() { configTzhaarHD = config.hdTzHaarReskin(); configProjectileLights = config.projectileLights(); configNpcLights = config.npcLights(); + waterTransparencyType = config.waterTransparencyType(); + waterTransparencyConfig = config.lightPenetrationPercentage(); configVanillaShadowMode = config.vanillaShadowMode(); configHideFakeShadows = configVanillaShadowMode != VanillaShadowMode.SHOW; configLegacyGreyColors = config.legacyGreyColors(); @@ -2676,6 +2736,8 @@ private void processPendingConfigChanges() { case KEY_PARALLAX_OCCLUSION_MAPPING: case KEY_UI_SCALING_MODE: case KEY_VANILLA_COLOR_BANDING: + case KEY_WATER_FOAM: + case KEY_WATER_LIGHT_SCATTERING: case KEY_LINEAR_ALPHA_BLENDING: case KEY_PLANAR_REFLECTION_RESOLUTION: recompilePrograms = true; @@ -2732,6 +2794,11 @@ private void processPendingConfigChanges() { restartPlugin(); // since we'll be restarting the plugin anyway, skip pending changes return; + case KEY_REPLACE_FISHING_SPOTS: + reloadModelOverrides = true; + fishingSpotReplacer.despawnRuneLiteObjects(); + clientThread.invokeLater(fishingSpotReplacer::update); + break; } } @@ -3002,7 +3069,9 @@ public void draw( eightIntWrite[6] = y + client.getCameraY2(); eightIntWrite[7] = z + client.getCameraZ2(); - int faceCount = 0; + int plane = ModelHash.getPlane(hash); + + int faceCount; if (sceneContext.id == (offsetModel.getSceneId() & SceneUploader.SCENE_ID_MASK)) { // The model is part of the static scene buffer assert model == renderable; @@ -3010,7 +3079,6 @@ public void draw( faceCount = Math.min(MAX_FACE_COUNT, offsetModel.getFaceCount()); int vertexOffset = offsetModel.getBufferOffset(); int uvOffset = offsetModel.getUvBufferOffset(); - int plane = (int) ((hash >> 49) & 3); boolean hillskew = offsetModel != model; eightIntWrite[0] = vertexOffset; @@ -3045,14 +3113,27 @@ public void draw( frameTimer.begin(Timer.MODEL_PUSHING); int uuid = ModelHash.generateUuid(client, hash, renderable); - int[] worldPos = HDUtils.cameraSpaceToWorldPoint(client, x, z); + int[] localPos = HDUtils.cameraSpaceToLocalPoint(client, x, z); + int[] worldPos = HDUtils.localToWorld(sceneContext.scene, localPos[0], localPos[1], plane); ModelOverride modelOverride = modelOverrideManager.getOverride(uuid, worldPos); if (modelOverride.hide) return; int vertexOffset = dynamicOffsetVertices + sceneContext.getVertexOffset(); int uvOffset = dynamicOffsetUvs + sceneContext.getUvOffset(); - modelPusher.pushModel(sceneContext, null, uuid, model, modelOverride, ObjectType.NONE, 0, true); + + int preOrientation = 0; + if (ModelHash.getType(hash) == ModelHash.TYPE_OBJECT) { + int tileExX = localPos[0] / LOCAL_TILE_SIZE + SCENE_OFFSET; + int tileExY = localPos[1] / LOCAL_TILE_SIZE + SCENE_OFFSET; + if (0 <= tileExX && tileExX < EXTENDED_SCENE_SIZE && 0 <= tileExY && tileExY < EXTENDED_SCENE_SIZE) { + int config = sceneContext.getObjectConfig(plane, tileExX, tileExY, hash); + preOrientation = HDUtils.getBakedOrientation(config); + } + } + + modelPusher.pushModel(sceneContext, null, uuid, model, modelOverride, ObjectType.NONE, preOrientation, true); + faceCount = sceneContext.modelPusherResults[0]; if (sceneContext.modelPusherResults[1] == 0) uvOffset = -1; @@ -3257,7 +3338,7 @@ public void onBeforeRender(BeforeRender beforeRender) { public void onClientTick(ClientTick clientTick) { elapsedClientTime += 1 / 50f; - if (skipScene != client.getScene()) + if (!enableFreezeFrame && skipScene != client.getScene()) redrawPreviousFrame = false; } @@ -3269,6 +3350,8 @@ public void onGameTick(GameTick gameTick) { --gameTicksUntilSceneReload; } + fishingSpotReplacer.update(); + // reload the scene if the player is in a house and their plane changed // this greatly improves the performance as it keeps the scene buffer up to date if (isInHouse) { diff --git a/src/main/java/rs117/hd/HdPluginConfig.java b/src/main/java/rs117/hd/HdPluginConfig.java index 0026cd4944..19afce2d3c 100644 --- a/src/main/java/rs117/hd/HdPluginConfig.java +++ b/src/main/java/rs117/hd/HdPluginConfig.java @@ -46,6 +46,7 @@ import rs117.hd.config.TextureResolution; import rs117.hd.config.UIScalingMode; import rs117.hd.config.VanillaShadowMode; +import rs117.hd.config.WaterTransparencyType; import static rs117.hd.HdPlugin.MAX_DISTANCE; import static rs117.hd.HdPlugin.MAX_FOG_DEPTH; @@ -618,37 +619,193 @@ default boolean groundBlending() return true; } + String KEY_HD_TZHAAR_RESKIN = "tzhaarHD"; + @ConfigItem( + keyName = KEY_HD_TZHAAR_RESKIN, + name = "HD TzHaar Reskin", + description = "Recolors the TzHaar city of Mor Ul Rek to give it an appearance similar to that of its 2008 HD variant.", + position = 10, + section = environmentSettings + ) + default boolean hdTzHaarReskin() { + return true; + } + + /*====== Water settings ======*/ + + @ConfigSection( + name = "Water", + description = "Water settings", + position = 3 + ) + String waterSettings = "waterSettings"; + + @ConfigItem( + keyName = "waterTransparencyType", + name = "Water Type", + description = + "Choose between a default of transparent or opaque water
" + + "'Transparent' shows underwater terrain and effects, while
" + + "'Static' does not.", + position = 1, + section = waterSettings + ) + default WaterTransparencyType waterTransparencyType() + { + return WaterTransparencyType.TRANSPARENT; + } + + String KEY_LIGHT_PENETRATION = "lightPenetrationPercentage"; + @ConfigItem( + keyName = KEY_LIGHT_PENETRATION, + name = "Light Penetration", + description = "Amount of light that can pass through transparent water.
Default 75%.", + position = 2, + section = waterSettings + ) + @Units(Units.PERCENT) + @Range(min = 0, max = 130) + default int lightPenetrationPercentage() { + return 75; + } + + String KEY_PLANAR_REFLECTIONS = "planarReflections"; + @ConfigItem( + keyName = KEY_PLANAR_REFLECTIONS, + name = "Planar Reflections", + description = "Render a highly detailed reflection of the game world on bodies of water.
GPU intensive.", + position = 3, + section = waterSettings + ) + default boolean enablePlanarReflections() { + return true; + } + + String KEY_PLANAR_REFLECTION_RESOLUTION = "planarReflectionResolution"; + @ConfigItem( + keyName = KEY_PLANAR_REFLECTION_RESOLUTION, + name = "Reflection Resolution", + description = "Percentage of screen resolution to render reflections at.
50% is a good balance of performance and quality.", + position = 4, + section = waterSettings + ) + @Units(Units.PERCENT) + @Range(min = 25, max = 100) + default int reflectionResolution() { + return 50; + } + + @ConfigItem( + keyName = "shorelineCaustics", + name = "Shoreline Caustics", + description = "Apply underwater lighting effects to imitate sunlight passing through waves on the surface.", + position = 5, + section = waterSettings + ) + default boolean shorelineCaustics() + { + return true; + } + @ConfigItem( keyName = "underwaterCaustics", name = "Underwater Caustics", description = "Apply underwater lighting effects to imitate sunlight passing through waves on the surface.", - position = 10, - section = environmentSettings + position = 6, + section = waterSettings ) default boolean underwaterCaustics() { return true; } - String KEY_HD_TZHAAR_RESKIN = "tzhaarHD"; + String KEY_WATER_CAUSTICS_STRENGTH_CONFIG = "waterCausticsStrengthConfig"; @ConfigItem( - keyName = KEY_HD_TZHAAR_RESKIN, - name = "HD TzHaar Reskin", - description = "Recolors the TzHaar city of Mor Ul Rek to give it an appearance similar to that of its 2008 HD variant.", + keyName = KEY_WATER_CAUSTICS_STRENGTH_CONFIG, + name = "Caustics Strength", + description = "Light strength for caustics.", + position = 7, + section = waterSettings + ) + @Units(Units.PERCENT) + @Range(min = 1, max = 200) + default int waterCausticsStrengthConfig() { + return 100; + } + + String KEY_WATER_WAVE_SIZE_CONFIG = "waterWaveSizeConfig"; + @ConfigItem( + keyName = KEY_WATER_WAVE_SIZE_CONFIG, + name = "Wave Size", + description = "The size of waves.", + position = 8, + section = waterSettings + ) + @Units(Units.PERCENT) + @Range(min = 25, max = 200) + default int waterWaveSizeConfig() { + return 100; + } + + String KEY_WATER_WAVE_SPEED_CONFIG = "waterWaveSpeedConfig"; + @ConfigItem( + keyName = KEY_WATER_WAVE_SPEED_CONFIG, + name = "Wave Speed", + description = "The speed of waves.", + position = 9, + section = waterSettings + ) + @Units(Units.PERCENT) + @Range(min = 50, max = 200) + default int waterWaveSpeedConfig() { + return 100; + } + + String KEY_WATER_LIGHT_SCATTERING = "waterLightScattering"; + @ConfigItem( + keyName = KEY_WATER_LIGHT_SCATTERING, + name = "Light Scattering", + description = "Approximate light scattering on water surface/waves.", + position = 10, + section = waterSettings + ) + default boolean waterLightScattering() { + return true; + } + + String KEY_WATER_FOAM = "waterFoam"; + @ConfigItem( + keyName = KEY_WATER_FOAM, + name = "Foam", + description = "Render foam around the edges of water bodies.", position = 11, - section = environmentSettings + section = waterSettings ) - default boolean hdTzHaarReskin() { + default boolean enableWaterFoam() { return true; } + String KEY_WATER_FOAM_AMOUNT_CONFIG = "waterFoamAmountConfig"; + @ConfigItem( + keyName = KEY_WATER_FOAM_AMOUNT_CONFIG, + name = "Foam Amount", + description = "The amount of foam around shorelines.", + position = 12, + section = waterSettings + ) + @Units(Units.PERCENT) + @Range(min = 1, max = 300) + default int waterFoamAmountConfig() { + return 100; + } + /*====== Model caching settings ======*/ @ConfigSection( name = "Model caching", description = "Improve performance by reusing model data", - position = 3, + position = 4, closedByDefault = true ) String modelCachingSettings = "modelCachingSettings"; @@ -707,7 +864,7 @@ default int modelCacheSizeMiBv1() @ConfigSection( name = "Miscellaneous", description = "Miscellaneous settings", - position = 4, + position = 5, closedByDefault = true ) String miscellaneousSettings = "miscellaneousSettings"; @@ -785,13 +942,25 @@ default boolean lowMemoryMode() { return false; } + String KEY_REPLACE_FISHING_SPOTS = "replaceFishingSpots"; + @ConfigItem( + keyName = KEY_REPLACE_FISHING_SPOTS, + name = "Replace Fishing Spots", + description = "Replace certain fishing spots with more appropriate models that are easier to see.", + position = 7, + section = miscellaneousSettings + ) + default boolean replaceFishingSpots() { + return true; + } + /*====== Experimental settings ======*/ @ConfigSection( name = "Experimental", description = "Experimental features - if you're experiencing issues you should consider disabling these", - position = 5, + position = 6, closedByDefault = true ) String experimentalSettings = "experimentalSettings"; @@ -864,32 +1033,6 @@ default boolean decoupleSkyAndWaterColor() { return false; } - String KEY_PLANAR_REFLECTIONS = "planarReflections"; - @ConfigItem( - keyName = KEY_PLANAR_REFLECTIONS, - name = "Planar Reflections", - description = "Render a highly detailed reflection of the game world on bodies of water. EXPENSIVE and WIP.", - position = 403, - section = experimentalSettings - ) - default boolean enablePlanarReflections() { - return false; - } - - String KEY_PLANAR_REFLECTION_RESOLUTION = "planarReflectionResolution"; - @ConfigItem( - keyName = KEY_PLANAR_REFLECTION_RESOLUTION, - name = "Reflection Resolution", - description = "Percentage of screen resolution", - position = 404, - section = experimentalSettings - ) - @Units(Units.PERCENT) - @Range(min = 1) - default int reflectionResolution() { - return 100; - } - String KEY_LINEAR_ALPHA_BLENDING = "experimentalLinearAlphaBlending"; @ConfigItem( keyName = KEY_LINEAR_ALPHA_BLENDING, diff --git a/src/main/java/rs117/hd/config/DefaultSkyColor.java b/src/main/java/rs117/hd/config/DefaultSkyColor.java index 03162d20bd..7280ea00cf 100644 --- a/src/main/java/rs117/hd/config/DefaultSkyColor.java +++ b/src/main/java/rs117/hd/config/DefaultSkyColor.java @@ -33,7 +33,8 @@ @RequiredArgsConstructor public enum DefaultSkyColor { - DEFAULT("117 HD Blue", 185, 214, 255), + //DEFAULT("117 HD Blue", 185, 214, 255), // launch 117 HD Blue + DEFAULT("117 HD Blue", 167, 193, 230), RUNELITE("RuneLite Skybox", -1, -1, -1), OSRS("Old School Black", 0, 0, 0), HD2008("2008 HD Tan", 200, 192, 169); diff --git a/src/main/java/rs117/hd/config/TextureResolution.java b/src/main/java/rs117/hd/config/TextureResolution.java index 5cd4fe2773..142cda0d3c 100644 --- a/src/main/java/rs117/hd/config/TextureResolution.java +++ b/src/main/java/rs117/hd/config/TextureResolution.java @@ -32,7 +32,8 @@ public enum TextureResolution { RES_128("128", 128), - RES_256("256", 256); + RES_256("256", 256), + RES_512("512", 512); private final String name; private final int size; diff --git a/src/main/java/rs117/hd/config/WaterTransparencyType.java b/src/main/java/rs117/hd/config/WaterTransparencyType.java new file mode 100644 index 0000000000..3893ca733b --- /dev/null +++ b/src/main/java/rs117/hd/config/WaterTransparencyType.java @@ -0,0 +1,6 @@ +package rs117.hd.config; + +public enum WaterTransparencyType { + TRANSPARENT, + OPAQUE +} diff --git a/src/main/java/rs117/hd/data/WaterType.java b/src/main/java/rs117/hd/data/WaterType.java index 37a67cd187..a94774b2d1 100644 --- a/src/main/java/rs117/hd/data/WaterType.java +++ b/src/main/java/rs117/hd/data/WaterType.java @@ -30,6 +30,7 @@ import lombok.experimental.Accessors; import rs117.hd.data.materials.Material; +import static rs117.hd.utils.ColorUtils.hsl; import static rs117.hd.utils.ColorUtils.rgb; import static rs117.hd.utils.ColorUtils.srgb; @@ -48,7 +49,8 @@ public enum WaterType .foamColor(srgb(115, 120, 101)) .depthColor(srgb(41, 82, 26)) .causticsStrength(0) - .duration(1.2f)), + .duration(1.2f) + .fishingSpotRecolor(hsl("#04730d"))), SWAMP_WATER_FLAT(SWAMP_WATER, true), POISON_WASTE(b -> b .specularStrength(.1f) @@ -134,6 +136,17 @@ public enum WaterType .foamColor(rgb(64, 64, 64)) .causticsStrength(0) .flat(true)), + DARK_BLUE_WATER(b -> b + .specularStrength(.1f) + .specularGloss(100) + .normalStrength(.1f) + .baseOpacity(.8f) + .fresnelAmount(.2f) + .surfaceColor(rgb("#07292f")) + .foamColor(rgb(64, 64, 64)) + .depthColor(rgb("#000000")) + .causticsStrength(0) + .flat(true)), ; public final boolean flat; @@ -149,6 +162,7 @@ public enum WaterType public final float causticsStrength; public final boolean hasFoam; public final float duration; + public final int fishingSpotRecolor; @Setter @Accessors(fluent = true) @@ -167,6 +181,7 @@ private static class Builder private float causticsStrength = 1; private boolean hasFoam = true; private float duration = 1; + private int fishingSpotRecolor = -1; } WaterType() @@ -191,6 +206,7 @@ private static class Builder causticsStrength = builder.causticsStrength; hasFoam = builder.hasFoam; duration = builder.duration; + fishingSpotRecolor = builder.fishingSpotRecolor; } WaterType(WaterType parent, boolean flat) @@ -208,5 +224,6 @@ private static class Builder causticsStrength = parent.causticsStrength; hasFoam = parent.hasFoam; duration = parent.duration; + fishingSpotRecolor = parent.fishingSpotRecolor; } } diff --git a/src/main/java/rs117/hd/data/environments/Area.java b/src/main/java/rs117/hd/data/environments/Area.java index 3cc9423a9a..ad593b1823 100644 --- a/src/main/java/rs117/hd/data/environments/Area.java +++ b/src/main/java/rs117/hd/data/environments/Area.java @@ -60,6 +60,7 @@ public enum Area new AABB(3084, 3048, 3124, 3006), // the node TUTORIAL_ISLAND_INSTANCE )), + TUTORIAL_ISLAND_CAVES(12436), // Lumbridge Interior LUMBRIDGE_CASTLE_BASEMENT(3205, 9613, 3220, 9626), @@ -1117,12 +1118,18 @@ public enum Area WILDERNESS_FOUNTAIN_OF_RUNE(3373, 3892, 3375, 3894), MAGE_ARENA_BANK(2527, 4725, 2549, 4708), MAGE_ARENA_GOD_STATUES(2520, 4730, 2495, 4682), - GIELINOR_SNOWY_NORTHERN_REGION( - new AABB(2942, 3711, 2748, 3980), - new AABB(2696, 3839, 2741, 3799), - new AABB(2714, 3803, 2757, 3767), + GIELINOR_SNOWY_NORTHERN_REGION(2942, 3711, 2748, 3980), + RELLEKKA_SNOWY_REGION( + new AABB(2702, 3779, 2747, 3744), + new AABB(2726, 3802, 2748, 3780), + new AABB(2711, 3800, 2725, 3780), new AABB(2723, 3743, 2758, 3710) ), + RELLEKKA_HUNTER_ICEBERG( + new AABB(2699, 3839, 2741, 3810), + new AABB(2711, 3809, 2725, 3801), + new AABB(2726, 3809, 2738, 3803) + ), // Trollheim - Weiss Region TROLLHEIM( regions( @@ -1194,10 +1201,8 @@ public enum Area new AABB(2824, 10227, 2830, 10223), // Sworp Shop 2 new AABB(2903, 10208, 2910, 10201), // Scluptors new AABB(2848, 10227, 2853, 10221) - - - ), + RED_AXE_REGION(7501), KELDAGRIM(regions(11422, 11423, 11678, 11679)), // Tirannwn @@ -1299,6 +1304,7 @@ public enum Area )), // Zeah + UNDERGROUND_OLD_ONES_RUINS(regions(5274, 5276, 5532, 5785)), KARUULM_SLAYER_DUNGEON( regions(5536), regionBox(5022, 5023), @@ -1347,13 +1353,16 @@ public enum Area new AABB(1625, 3905, 1856, 3817) ), ZEAH_SNOWY_NORTHERN_REGION( - new AABB(1896, 3902, 1413, 4058), new AABB(1852, 3901, 1914, 3839), new AABB(1503, 3901, 1636, 3887), new AABB(1513, 3886, 1622, 3876), new AABB(1518, 3875, 1600, 3839), - new AABB(1637, 3901, 1668, 3894) + new AABB(1637, 3901, 1668, 3894), + new AABB(1407, 3902, 1900, 3965), + new AABB(1655, 3966, 1818, 4058), + new AABB(1605, 3966, 1503, 4058) ), + WINTERTODT_ARENA(1606, 3966, 1654, 4058), LOVAKENGJ( new AABB(1421, 3844, 1560, 3788), new AABB(1418, 3878, 1517, 3845), @@ -1433,6 +1442,7 @@ public enum Area THE_STRANGLEWOOD_QUEST_UNDERGROUND_AREAS(regionBox(4760, 4761)), JUDGE_OF_YAMA_BOSS(regionBox(6492, 6748)), XAMPHUR_BOSS(12124), + ZEAH_UPPER_LEVELS(new AABB(1085, 4078, 1, 1938, 2870, 3)), ZEAH(1085, 4078, 1938, 2870), // Fossil Island @@ -1898,6 +1908,9 @@ public enum Area RANDOM_EVENT_PRISON_PETE(2059, 4479, 2111, 4447), RANDOM_EVENT_QUIZ_MASTER(7754), + // Holiday events + CHURCH_OF_AYASTER(15455), + BLACK_ROOMS( RFD_QUIZ, RANDOM_EVENT_QUIZ_MASTER @@ -2040,25 +2053,7 @@ public enum Area DAGANNOTH_KINGS_SLAYER_CAVE )), - // SNOWY REGIONS ESSENCE_MINE(11595), - SNOW_REGIONS( - ASGARNIA_ICE_DUNGEON_SNOWY, - ESSENCE_MINE, - FREMENNIK_ISLES_NORTH, - FROZEN_WASTE_PLATEAU, - GIELINOR_SNOWY_NORTHERN_REGION, - GOBLIN_VILLAGE_ICE_CAVE, - ICEBERG, - ICE_MOUNTAIN, - ICE_QUEENS_DUNGEON, - LUNAR_ISLE, - MISCELLANIA, - WHITE_WOLF_MOUNTAIN, - ZEAH_SNOWY_NORTHERN_REGION, - PLAYER_OWNED_HOUSE_SNOWY, - TROLLHEIM - ), KINGDOM_OF_MISTHALIN_REGION( new AABB(3067, 3521, 3399, 3330), new AABB(3070, 3329, 3263, 3201), @@ -2207,10 +2202,110 @@ public enum Area new AABB(2576, 3012, 2652, 2997), new AABB(2438, 2996, 2657, 2816) ), + RED_CHINCHOMPA_HUNTING_GROUND(2510, 9308, 2533, 9286), CERBERUS(1216, 1216, 1406, 1343), // Abyss ABYSS(regions(12106, 12107, 12108, 11850, 11851, 12362, 12363)), + // Varlamore + VARLAMORE(regionBox(4908, 7476)), + VARLAMORE_TEMPLE_CRYPT( + new AABB(1608, 9536, 1663, 9586), + new AABB(1664, 9433, 1727, 9548), + new AABB(1685, 9549, 1714, 9579) + ), + VARLAMORE_TEMPLE_CRYPT_CENTER( + new AABB(1683, 9513, 1686, 9516, 0) + ), + VARLAMORE_TEMPLE_CRYPT_FAKE_BRICK_TILES( + new AABB(1678, 9523, 1679, 9526, 0), + new AABB(1673, 9520, 1676, 9521, 0), + new AABB(1679, 9512, 1680, 9517, 0), + new AABB(1673, 9508, 1676, 9509, 0), + new AABB(1678, 9503, 1679, 9506, 0), + new AABB(1682, 9509, 1687, 9510, 0), + new AABB(1690, 9503, 1691, 9506, 0), + new AABB(1693, 9508, 1696, 9509, 0), + new AABB(1689, 9512, 1690, 9517, 0), + new AABB(1693, 9520, 1694, 9521, 0), + new AABB(1695, 9520, 1696, 9520, 0), + new AABB(1696, 9521), + new AABB(1690, 9523, 1691, 9526, 0), + new AABB(1682, 9519, 1687, 9520, 0) + ), + VARLAMORE_TEMPLE_CRYPT_FAKE_SQUARE_TILES( + new AABB(1677, 9507, 1681, 9511, 0), + new AABB(1677, 9518, 1681, 9522, 0), + new AABB(1688, 9518, 1692, 9522, 0), + new AABB(1688, 9507, 1692, 9511, 0) + ), + HUNTER_GUILD(6291), + HUNTER_GUILD_SURFACE(1535, 3024, 1575, 3070), + VARLAMORE_SNOWY_MOUNTAINS(merge( + regionBox(5680, 5682), + regionBox(5938, 6706) + )), + VARLAMORE_SAVANNA_HUNTER_PIT_TRAPS(new AABB(1731, 2992, 1764, 3024, 0)), + VARLAMORE_CAM_TORUM_ENTRANCE_PATH_BLENDING_FIX(1443, 3104, 1444, 3105), + CAM_TORUM( + new AABB(1384, 9535, 1385, 9539), + new AABB(1386, 9535, 1405, 9577), + new AABB(1406, 9504, 1494, 9605), + new AABB(1495, 9504, 1534, 9561), + new AABB(1495, 9562, 1497, 9576), + new AABB(1498, 9562, 1498, 9575), + new AABB(1499, 9562, 1500, 9572), + new AABB(1501, 9562, 1501, 9571), + new AABB(1502, 9562, 1502, 9568), + new AABB(1503, 9562, 1505, 9566), + new AABB(1506, 9562, 1508, 9565) + ), + EARTHBOUND_CAVERN(1344, 9664, 1407, 9747), + STREAMBOUND_CAVERN(6039), + ANCIENT_PRISON(1344, 9536, 1391, 9599), + ANCIENT_SHRINE( + new AABB(1501, 9568, 1525, 9592, 0), + new AABB(1493, 9571, 1500, 9588, 0), + new AABB(1506, 9593, 1520, 9598, 0), + new AABB(1526, 9573, 1532, 9588, 0), + new AABB(1506, 9561, 1520, 9567, 0) + ), + THE_BLUE_MOON( + new AABB(1434, 9649, 1446, 9663, 0), + new AABB(1420, 9664, 1460, 9700, 0) + ), + THE_BLOOD_MOON_ENTRANCE(new AABB(1408, 9626, 1422, 9638, 0)), + THE_BLOOD_MOON(merge( + THE_BLOOD_MOON_ENTRANCE, + new AABB(1370, 9610, 1407, 9654, 0) + )), + THE_ECLIPSE_MOON_ENTRANCE(new AABB(1457, 9626, 1471, 9638, 0)), + THE_ECLIPSE_MOON(merge( + THE_ECLIPSE_MOON_ENTRANCE, + new AABB(1472, 9610, 1510, 9654, 0) + )), + COLOSSEUM(7316), + + SNOWY_REGIONS( + ASGARNIA_ICE_DUNGEON_SNOWY, + ESSENCE_MINE, + FREMENNIK_ISLES_NORTH, + FROZEN_WASTE_PLATEAU, + GIELINOR_SNOWY_NORTHERN_REGION, + RELLEKKA_SNOWY_REGION, + GOBLIN_VILLAGE_ICE_CAVE, + ICEBERG, + ICE_MOUNTAIN, + ICE_QUEENS_DUNGEON, + LUNAR_ISLE, + MISCELLANIA, + WHITE_WOLF_MOUNTAIN, + ZEAH_SNOWY_NORTHERN_REGION, + PLAYER_OWNED_HOUSE_SNOWY, + TROLLHEIM, + VARLAMORE_SNOWY_MOUNTAINS + ), + MAINLAND(1024, 4159, 3967, 2496), MAINLAND_EXTENSIONS(merge( BRAINDEATH_ISLAND, @@ -2252,6 +2347,7 @@ public enum Area SHIP_SAILING, VER_SINHAZA_CUTSCENE, DEFENDER_OF_VARROCK_CASTLE_UNDER_ATTACK, + CHURCH_OF_AYASTER, regions(8288, 8801, 11593) )), OVERWORLD( diff --git a/src/main/java/rs117/hd/data/materials/GroundMaterial.java b/src/main/java/rs117/hd/data/materials/GroundMaterial.java index d0c1a29bc0..681073cacb 100644 --- a/src/main/java/rs117/hd/data/materials/GroundMaterial.java +++ b/src/main/java/rs117/hd/data/materials/GroundMaterial.java @@ -96,7 +96,7 @@ public enum GroundMaterial { CLEAN_TILE(Material.CLEAN_TILE), WORN_TILES(Material.WORN_TILES), WATER_FLAT(Material.WATER_FLAT), - HD_WOOD_PLANKS_1(Material.HD_WOOD_PLANKS_1), + HD_WOOD_PLANKS_2(Material.HD_WOOD_PLANKS_2), ICE_1(Material.ICE_1), ICE_1_HIGHGLOSS(Material.ICE_1_HIGHGLOSS), ICE_4(Material.ICE_4), diff --git a/src/main/java/rs117/hd/data/materials/Material.java b/src/main/java/rs117/hd/data/materials/Material.java index 8add7b06bb..fce9fa879e 100644 --- a/src/main/java/rs117/hd/data/materials/Material.java +++ b/src/main/java/rs117/hd/data/materials/Material.java @@ -305,6 +305,7 @@ public enum Material { GRAY_50(NONE, p -> p.setBrightness(ColorUtils.srgbToLinear(.5f))), GRAY_25(NONE, p -> p.setBrightness(ColorUtils.srgbToLinear(.25f))), BLACK(NONE, p -> p.setBrightness(0)), + PURE_BLACK(NONE, p -> p.setOverrideBaseColor(true).setBrightness(0)), BLANK_GLOSS(WHITE, p -> p .setSpecular(0.9f, 280)), @@ -407,6 +408,11 @@ public enum Material { .setSpecular(0.4f, 20) .setBrightness(1.2f) ), + ROCK_3_SMOOTH(ROCK_3, p -> p + .setDisplacementScale(0) + .setTextureScale(1, 1, .15f) + .setSpecular(0.3f, 40) + ), ROCK_3_ORE(ROCK_3, p -> p .setSpecular(1, 20) ), @@ -523,15 +529,24 @@ public enum Material { .setSpecular(0.3f, 30) ), LIGHT_BARK(BARK, p -> p.setBrightness(1.75f)), + VERY_LIGHT_BARK(BARK, p -> p.setBrightness(2.75f)), WOOD_GRAIN, WOOD_GRAIN_2_N, WOOD_GRAIN_2(p -> p .setNormalMap(WOOD_GRAIN_2_N) .setSpecular(0.3f, 30) ), + WOOD_GRAIN_2_SMOOTH(WOOD_GRAIN_2, p -> p + .setBrightness(1.1f) + .setTextureScale(1, 1, .2f) + ), WOOD_GRAIN_2_LIGHT(WOOD_GRAIN_2, p -> p .setBrightness(1.1f) ), + WOOD_GRAIN_2_SMOOTH_LIGHT(WOOD_GRAIN_2, p -> p + .setBrightness(1.3f) + .setTextureScale(1, 1, .2f) + ), WOOD_GRAIN_2_WIDE(WOOD_GRAIN_2, p -> p .setTextureScale(1.5f, 0.5f) ), @@ -633,6 +648,8 @@ public enum Material { .setSpecular(0.7f, 80)), METALLIC_2_HIGHGLOSS(METALLIC_2, p -> p .setSpecular(1.1f, 80)), + METALLIC_2_SUPER_HIGHGLOSS(METALLIC_2, p -> p + .setSpecular(2.25f, 65)), METALLIC_NONE_GLOSS(NONE, p -> p .setSpecular(0.7f, 80)), WATTLE_1, @@ -655,9 +672,22 @@ public enum Material { .setSpecular(1.5f, 80)), HD_WOOD_PLANKS_1_N, HD_WOOD_PLANKS_1(p -> p + .replaceIf(plugin -> plugin.configModelTextures, WOOD_PLANKS_1) .setNormalMap(HD_WOOD_PLANKS_1_N) + .setSpecular(0.3f, 30) + ), + HD_WOOD_PLANKS_2_N, + HD_WOOD_PLANKS_2(p -> p + .setNormalMap(HD_WOOD_PLANKS_2_N) .setSpecular(0.3f, 40) .setBrightness(1.2f)), + HD_CRATE_N, + HD_CRATE(p -> p + .replaceIf(plugin -> plugin.configModelTextures, CRATE) + .setNormalMap(HD_CRATE_N) + .setSpecular(0.25f, 30) + .setBrightness(0.9f) + ), HD_ROOF_BRICK_TILE_N, HD_ROOF_BRICK_TILE(ROOF_BRICK_TILE, p -> p .replaceIf(plugin -> plugin.configModelTextures, ROOF_BRICK_TILE) @@ -870,7 +900,7 @@ private static class Builder { private float specularStrength; private float specularGloss; private float[] scrollSpeed = { 0, 0 }; - private float[] textureScale = { 1, 1 }; + private float[] textureScale = { 1, 1, 1 }; private List materialsToReplace = new ArrayList<>(); private Function replacementCondition; @@ -921,8 +951,9 @@ Builder setScroll(float speedX, float speedY) { return this; } - Builder setTextureScale(float x, float y) { - this.textureScale = new float[] { x, y }; + Builder setTextureScale(float... xyz) { + textureScale = Arrays.copyOf(textureScale, 3); + System.arraycopy(xyz, 0, textureScale, 0, Math.min(3, xyz.length)); return this; } @@ -962,7 +993,7 @@ Builder replaceIf(SeasonalTheme seasonalTheme, @NonNull Material... materialsToR consumer.accept(builder); parent = builder.parent; normalMap = builder.normalMap; - displacementMap = builder.displacementMap; + displacementMap = builder.displacementScale == 0 ? null : builder.displacementMap; roughnessMap = builder.roughnessMap; ambientOcclusionMap = builder.ambientOcclusionMap; flowMap = builder.flowMap; diff --git a/src/main/java/rs117/hd/overlays/FrameTimer.java b/src/main/java/rs117/hd/overlays/FrameTimer.java index e2a8a249d9..464dbf7d4d 100644 --- a/src/main/java/rs117/hd/overlays/FrameTimer.java +++ b/src/main/java/rs117/hd/overlays/FrameTimer.java @@ -61,14 +61,14 @@ public interface Listener { } public void addTimingsListener(Listener listener) { - if (listeners.size() == 0) + if (listeners.isEmpty()) initialize(); listeners.add(listener); } public void removeTimingsListener(Listener listener) { listeners.remove(listener); - if (listeners.size() == 0) + if (listeners.isEmpty()) destroy(); } diff --git a/src/main/java/rs117/hd/overlays/FrameTimingsOverlay.java b/src/main/java/rs117/hd/overlays/FrameTimerOverlay.java similarity index 82% rename from src/main/java/rs117/hd/overlays/FrameTimingsOverlay.java rename to src/main/java/rs117/hd/overlays/FrameTimerOverlay.java index 2a352c6f33..c78c18b289 100644 --- a/src/main/java/rs117/hd/overlays/FrameTimingsOverlay.java +++ b/src/main/java/rs117/hd/overlays/FrameTimerOverlay.java @@ -15,7 +15,7 @@ import rs117.hd.HdPlugin; @Singleton -public class FrameTimingsOverlay extends OverlayPanel implements FrameTimer.Listener { +public class FrameTimerOverlay extends OverlayPanel implements FrameTimer.Listener { @Inject private OverlayManager overlayManager; @@ -25,7 +25,7 @@ public class FrameTimingsOverlay extends OverlayPanel implements FrameTimer.List private final ArrayDeque frames = new ArrayDeque<>(); @Inject - public FrameTimingsOverlay(HdPlugin plugin) { + public FrameTimerOverlay(HdPlugin plugin) { super(plugin); setLayer(OverlayLayer.ABOVE_SCENE); setPosition(OverlayPosition.TOP_RIGHT); @@ -46,7 +46,7 @@ public void setActive(boolean activate) { @Override public void onFrameCompletion(FrameTimings timings) { long now = System.nanoTime(); - while (frames.size() > 0) { + while (!frames.isEmpty()) { if (now - frames.peekFirst().frameTimestamp < 3e9) // remove entries older than 3 seconds break; frames.removeFirst(); @@ -62,19 +62,16 @@ public Dimension render(Graphics2D g) { .text("Waiting for data...") .build()); } else { - long cpuTime = timings[Timer.DRAW_SCENE.ordinal()]; + long cpuTime = timings[Timer.DRAW_FRAME.ordinal()]; addTiming("CPU", cpuTime, true); for (var t : Timer.values()) - if (!t.isGpuTimer) + if (!t.isGpuTimer && t != Timer.DRAW_FRAME) addTiming(t, timings); - long gpuTime = - timings[Timer.UPLOAD_GEOMETRY.ordinal()] + - timings[Timer.UPLOAD_UI.ordinal()] + - timings[Timer.COMPUTE.ordinal()] + - timings[Timer.RENDER_SHADOWS.ordinal()] + - timings[Timer.RENDER_SCENE.ordinal()] + - timings[Timer.RENDER_UI.ordinal()]; + long gpuTime = 0; + for (var t : Timer.values()) + if (t.isGpuTimer) + gpuTime += timings[t.ordinal()]; addTiming("GPU", gpuTime, true); for (var t : Timer.values()) if (t.isGpuTimer) @@ -82,7 +79,14 @@ public Dimension render(Graphics2D g) { panelComponent.getChildren().add(LineComponent.builder() .leftFont(FontManager.getRunescapeBoldFont()) - .left("Max Frame Rate:") + .left("Estimated bottleneck:") + .rightFont(FontManager.getRunescapeBoldFont()) + .right(cpuTime > gpuTime ? "CPU" : "GPU") + .build()); + + panelComponent.getChildren().add(LineComponent.builder() + .leftFont(FontManager.getRunescapeBoldFont()) + .left("Estimated FPS:") .rightFont(FontManager.getRunescapeBoldFont()) .right(String.format("%.1f FPS", 1 / (Math.max(cpuTime, gpuTime) / 1e9))) .build()); @@ -92,7 +96,7 @@ public Dimension render(Graphics2D g) { } private long[] getAverageTimings() { - if (frames.size() == 0) + if (frames.isEmpty()) return new long[0]; long[] timers = new long[Timer.values().length]; diff --git a/src/main/java/rs117/hd/overlays/LightGizmoOverlay.java b/src/main/java/rs117/hd/overlays/LightGizmoOverlay.java index f7a0cc4dff..860326c5e8 100644 --- a/src/main/java/rs117/hd/overlays/LightGizmoOverlay.java +++ b/src/main/java/rs117/hd/overlays/LightGizmoOverlay.java @@ -6,14 +6,22 @@ import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.MouseInfo; import java.awt.Stroke; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; import javax.inject.Inject; import javax.inject.Singleton; import javax.swing.SwingUtilities; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; +import net.runelite.client.callback.ClientThread; import net.runelite.client.input.KeyListener; import net.runelite.client.input.KeyManager; import net.runelite.client.input.MouseListener; @@ -23,14 +31,17 @@ import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.util.ColorUtil; import org.apache.commons.lang3.NotImplementedException; import rs117.hd.HdPlugin; -import rs117.hd.scene.LightManager; +import rs117.hd.scene.lights.Alignment; import rs117.hd.scene.lights.Light; import rs117.hd.scene.lights.LightType; -import rs117.hd.utils.HDUtils; +import rs117.hd.utils.ColorUtils; import rs117.hd.utils.Mat4; +import rs117.hd.utils.Vector; +import static net.runelite.api.Perspective.*; import static rs117.hd.HdPlugin.NEAR_PLANE; @Slf4j @@ -41,6 +52,9 @@ public class LightGizmoOverlay extends Overlay implements MouseListener, KeyList @Inject private Client client; + @Inject + private ClientThread clientThread; + @Inject private OverlayManager overlayManager; @@ -53,19 +67,47 @@ public class LightGizmoOverlay extends Overlay implements MouseListener, KeyList @Inject private HdPlugin plugin; - @Inject - private LightManager lightManager; - - private Light selected; - private Light hovered; - - private boolean hideInvisibleLights = true; + private boolean hideInvisibleLights; private boolean hideRadiusRings = true; private boolean hideAnimLights; private boolean hideLabels; - private boolean hideInfo; + private boolean hideInfo = true; private boolean toggleBlackColor; - private boolean liveInfo = false; + private boolean liveInfo; + private boolean showDuplicationInfo; + private boolean toggleOpacity = true; + private boolean followMouse; + + private Action action = Action.SELECT; + private final double[] rawMousePos = new double[2]; + private final double[] rawMousePosPrev = new double[2]; + private final double[] mouseDelta = new double[2]; + private final float[] cameraOrientation = new float[2]; + private Alignment originalLightAlignment = Alignment.CUSTOM; + private final int[] originalLightPosition = new int[3]; + private final int[] originalLightOffset = new int[3]; + private final int[] currentLightOffset = new int[3]; + private int freezeMode = 0; + private final boolean[] frozenAxes = { false, true, false }; // by default, restrict movement to the same height + private final ArrayList selections = new ArrayList<>(); + private final ArrayList hovers = new ArrayList<>(); + private boolean isProbablyRotatingCamera; + + private static final int RELATIVE_TO_CAMERA = 0; + private static final int RELATIVE_TO_ORIGIN = 1; + private static final int RELATIVE_TO_POSITION = 2; + + // TODO: implement undo & redo + private ArrayDeque history = new ArrayDeque<>(); + + interface Change { + void undo(); + void redo(); + } + + enum Action { + SELECT, GRAB, SCALE + } public LightGizmoOverlay() { setLayer(OverlayLayer.ABOVE_SCENE); @@ -81,6 +123,8 @@ public void setActive(boolean activate) { overlayManager.remove(this); mouseManager.unregisterMouseListener(this); keyManager.unregisterKeyListener(this); + action = Action.SELECT; + selections.clear(); } } @@ -90,9 +134,47 @@ public Dimension render(Graphics2D g) { if (sceneContext == null) return null; - Point mousePos = client.getMouseCanvasPosition(); - if (mousePos != null && (mousePos.getX() == -1 || mousePos.getY() == -1)) - mousePos = null; + // If the orientation changed, don't consider mouse movement + boolean wasCameraReoriented = isProbablyRotatingCamera; + for (int j = 0; j < 2; j++) { + if (cameraOrientation[j] != plugin.cameraOrientation[j]) { + wasCameraReoriented = true; + break; + } + } + System.arraycopy(plugin.cameraOrientation, 0, cameraOrientation, 0, 2); + + boolean isCtrlHeld = client.isKeyPressed(KeyCode.KC_CONTROL); + boolean isShiftHeld = client.isKeyPressed(KeyCode.KC_SHIFT); + boolean isAltHeld = client.isKeyPressed(KeyCode.KC_ALT); + + var rawMouse = MouseInfo.getPointerInfo().getLocation(); + rawMousePos[0] = (float) rawMouse.getX(); + rawMousePos[1] = (float) rawMouse.getY(); + if (wasCameraReoriented) { + if (action == Action.GRAB) { + assert !selections.isEmpty(); + // Rotation & moving the light with the mouse don't mix very well, so apply the offset and reset mouseDelta when rotating + if (mouseDelta[0] != 0 || mouseDelta[1] != 0) { + Arrays.fill(mouseDelta, 0); + var selection = selections.get(0); + System.arraycopy(selection.offset, 0, currentLightOffset, 0, 3); + } + } + } else if (!isAltHeld) { + double scalingFactor = isShiftHeld ? .1 : 1; + for (int j = 0; j < 2; j++) + mouseDelta[j] += (rawMousePos[j] - rawMousePosPrev[j]) * scalingFactor; + } + System.arraycopy(rawMousePos, 0, rawMousePosPrev, 0, 2); + + var mousePoint = new java.awt.Point((int) Math.round(rawMousePos[0]), (int) Math.round(rawMousePos[1])); + SwingUtilities.convertPointFromScreen(mousePoint, client.getCanvas()); + int[] mousePos = { mousePoint.x, mousePoint.y }; + + Point mousePosCanvas = client.getMouseCanvasPosition(); + if (mousePosCanvas != null && (mousePosCanvas.getX() == -1 || mousePosCanvas.getY() == -1)) + mousePosCanvas = null; g.setFont(FontManager.getRunescapeSmallFont()); @@ -101,6 +183,7 @@ public Dimension render(Graphics2D g) { final int outerHandleRingDiameter = 25; final int hoverDistanceMargin = 5; + Stroke thickLine = new BasicStroke(2); Stroke thinLine = new BasicStroke(1); Stroke thinnerLine = new BasicStroke(.75f); Stroke thinDashedLine = new BasicStroke( @@ -111,14 +194,18 @@ public Dimension render(Graphics2D g) { 1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 10 }, 0 ); + Stroke thickDashedLine = new BasicStroke( + 1.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + 0, new float[] { 3 }, 0 + ); float[] projectionMatrix = Mat4.identity(); int viewportWidth = client.getViewportWidth(); int viewportHeight = client.getViewportHeight(); Mat4.mul(projectionMatrix, Mat4.translate(client.getViewportXOffset(), client.getViewportYOffset(), 0)); Mat4.mul(projectionMatrix, Mat4.scale(viewportWidth, viewportHeight, 1)); + Mat4.mul(projectionMatrix, Mat4.translate(.5f, .5f, .5f)); Mat4.mul(projectionMatrix, Mat4.scale(.5f, -.5f, .5f)); - Mat4.mul(projectionMatrix, Mat4.translate(1, -1, 1)); // NDC clip space Mat4.mul(projectionMatrix, Mat4.scale(client.getScale(), client.getScale(), 1)); Mat4.mul(projectionMatrix, Mat4.projection(viewportWidth, viewportHeight, NEAR_PLANE)); @@ -130,26 +217,205 @@ public Dimension render(Graphics2D g) { -plugin.cameraPosition[2] )); - hovered = null; + float[] inverseProjection = null; + try { + inverseProjection = Mat4.inverse(projectionMatrix); + } catch (IllegalArgumentException ex) { + System.out.println("Not invertible"); + } + + int numFrozenAxes = 0; + if (freezeMode > 0) + for (int i = 0; i < 3; i++) + if (frozenAxes[i]) + numFrozenAxes++; + + hovers.clear(); + int counter = 0; final float[] lightToCamera = new float[3]; - for (Light l : sceneContext.lights) { + var lights = sceneContext.lights; + + float[] point = new float[4]; + int selectedIndex = -1; + for (int i = lights.size() - 1; i >= -1; i--) { + // Draw the selected light last + int lightIndex = i; + if (i == -1) { + lightIndex = selectedIndex; + if (lightIndex == -1) + continue; + } + + Light l = lights.get(lightIndex); + if (hideInvisibleLights && !l.def.visibleFromOtherPlanes && (l.plane < client.getPlane() && l.belowFloor || l.plane > client.getPlane() && l.aboveFloor)) continue; - if (hideAnimLights && !l.def.animationIds.isEmpty() && !l.visible) + if (hideAnimLights && !l.def.animationIds.isEmpty() && !l.parentExists) continue; - float[] lightPos = new float[] { l.x, l.y, l.z, 1 }; - float[] point = projectPoint(projectionMatrix, lightPos); + boolean isHovered = !hovers.isEmpty() && hovers.get(0) == l; + boolean isSelected = selections.contains(l); + + // Skip the selected light until the end + if (i != -1 && isSelected) { + selectedIndex = i; + continue; + } + + if (isSelected && !wasCameraReoriented && inverseProjection != null) { + if (action == Action.GRAB) { + float[] oldLightPos = new float[4]; + float[] newLightPos = new float[4]; + + float radians = (float) (l.orientation * UNIT); + float sin = (float) Math.sin(radians); + float cos = (float) Math.cos(radians); + + // Project the light's current position into screen space + for (int j = 0; j < 3; j++) + oldLightPos[j] = l.origin[j]; + float x = currentLightOffset[0]; + float z = currentLightOffset[2]; + oldLightPos[0] += -cos * x - sin * z; + oldLightPos[1] += currentLightOffset[1]; + oldLightPos[2] += -cos * z + sin * x; + oldLightPos[3] = 1; + Mat4.projectVec(point, projectionMatrix, oldLightPos); + + if (followMouse) { + // Move the light to the mouse position + for (int j = 0; j < 2; j++) + point[j] = mousePos[j]; + } else { + // Shift the position with mouse movement + for (int j = 0; j < 2; j++) + point[j] += (float) mouseDelta[j]; + } + + if (numFrozenAxes == 0) { // restrict to same depth plane + // Project the screen position back into the new light position + Mat4.projectVec(newLightPos, inverseProjection, point); + if (point[3] <= 0) + continue; + } else { + // p1 & v1 = ray from the camera in the hovered direction + var p1 = plugin.cameraPosition; + var v1 = new float[3]; + + // Compute a vector from the camera to the target mouse position + Mat4.projectVec(point, inverseProjection, point); + for (int j = 0; j < 3; j++) + v1[j] = point[j] - p1[j]; + + if (numFrozenAxes == 1) { + // restrict to basis plane + // ax + by + cz = d + // n = (a, b, c) + float[] n = new float[3]; + for (int j = 0; j < 3; j++) { + if (frozenAxes[j]) { + n[j] = 1; + break; + } + } + + if (freezeMode == RELATIVE_TO_ORIGIN) { + for (int j = 0; j < 3; j++) + oldLightPos[j] = l.origin[j]; + oldLightPos[3] = 1; + Mat4.projectVec(point, projectionMatrix, oldLightPos); + } + + float d = Vector.dot(n, oldLightPos); + + // dot(p1 + v1 * t, n) = d + // dot(p1, n) + dot(v1 * t, n) = d + // dot(p1, n) + dot(v1, n) * t = d + // t = (d - dot(p1, n)) / dot(v1, n) + float t = (d - Vector.dot(p1, n)) / Vector.dot(v1, n); + + for (int j = 0; j < 3; j++) + newLightPos[j] = p1[j] + v1[j] * t; + } else if (numFrozenAxes == 2) { + // restrict to axis + int axis = 0; + for (int j = 0; j < 3; j++) { + if (!frozenAxes[j]) { + axis = j; + break; + } + } + + // p2 & v2 = ray from the light's origin in the direction of the target axis + var p2 = new float[3]; + var origin = freezeMode == RELATIVE_TO_ORIGIN ? l.origin : originalLightPosition; + for (int j = 0; j < 3; j++) + p2[j] = origin[j]; + var v2 = new float[3]; + v2[axis] = 1; + + // v3 is the direction perpendicular to both v1 and v2, which is the direction + // for the shortest path between two points on the two rays + var v3 = new float[3]; + Vector.cross(v3, v1, v2); + + try { + // Solve the following set of linear equations to find t2; the distance + // from p2 along v2 until the closest point between the two rays: + // p1 + v1 * t1 + v3 * t3 = p2 + v2 * t2 + + // Solve for t2: + float t2 = -p1[0] * v1[1] * v3[2] + p1[0] * v1[2] * v3[1] + p1[1] * v1[0] * v3[2] - p1[1] * v1[2] * v3[0] + - p1[2] * v1[0] * v3[1] + p1[2] * v1[1] * v3[0] + p2[0] * v1[1] * v3[2] - p2[0] * v1[2] * v3[1] + - p2[1] * v1[0] * v3[2] + p2[1] * v1[2] * v3[0] + p2[2] * v1[0] * v3[1] - p2[2] * v1[1] * v3[0]; + t2 /= v1[0] * v2[1] * v3[2] - v1[0] * v2[2] * v3[1] - v1[1] * v2[0] * v3[2] + v1[1] * v2[2] * v3[0] + + v1[2] * v2[0] * v3[1] - v1[2] * v2[1] * v3[0]; + + for (int j = 0; j < 3; j++) + newLightPos[j] = p2[j] + v2[j] * t2; + } catch (IllegalArgumentException ex) { + log.debug("No solution:", ex); + } + } + } + + float gridSize = isCtrlHeld ? 128f / (isShiftHeld ? 8 : 1) : 1; + + float[] relativePos = new float[3]; + for (int j = 0; j < 3; j++) + relativePos[j] = newLightPos[j] - l.origin[j]; + + x = relativePos[0]; + z = relativePos[2]; + relativePos[0] = -cos * x + sin * z; + relativePos[2] = -cos * z - sin * x; + + for (int j = 0; j < 3; j++) + l.offset[j] = (int) (Math.round(relativePos[j] / gridSize) * gridSize); + + x = l.offset[0]; + z = l.offset[2]; + l.pos[0] = l.origin[0] + (int) (-cos * x - sin * z); + l.pos[1] = l.origin[1] + l.offset[1]; + l.pos[2] = l.origin[2] + (int) (-cos * z + sin * x); + } + } + + for (int j = 0; j < 3; j++) + point[j] = l.pos[j]; + point[3] = 1; + + Vector.subtract(lightToCamera, plugin.cameraPosition, point); + float distanceFromCamera = Vector.length(lightToCamera); + + Mat4.projectVec(point, projectionMatrix, point); if (point[3] <= 0) continue; int x = Math.round(point[0]); int y = Math.round(point[1]); - HDUtils.subtract(lightToCamera, plugin.cameraPosition, lightPos); - float distanceFromCamera = HDUtils.length(lightToCamera); - // Take perspective depth into account int currentDiameter = Math.round(l.radius * 2 / distanceFromCamera * client.getScale()); float definedDiameter = l.def.radius * 2 / distanceFromCamera * client.getScale(); @@ -157,34 +423,37 @@ public Dimension render(Graphics2D g) { int minDiameter = Math.round(definedDiameter * (1 - fRange)); int maxDiameter = Math.round(definedDiameter * (1 + fRange)); - if (mousePos != null && hovered == null) { - float d = HDUtils.length(mousePos.getX() - x, mousePos.getY() - y); + if (mousePosCanvas != null) { + float d = Vector.length(mousePosCanvas.getX() - x, mousePosCanvas.getY() - y); if (d <= outerHandleRingDiameter / 2f + hoverDistanceMargin || !hideRadiusRings && Math.abs(d - currentDiameter / 2f) < hoverDistanceMargin * 2) - hovered = l; + hovers.add(l); } - boolean isHovered = hovered == l; - boolean isSelected = selected == l; - - int mainOpacity = l.visible ? 255 : 100; + int mainOpacity = toggleOpacity ? + (l.visible ? 255 : 100) : + (l.visible ? 100 : 30); int rangeOpacity = 70; Color baseColor = toggleBlackColor ? Color.BLACK : Color.WHITE; Color radiusRingColor = alpha(baseColor, mainOpacity); Color rangeRingsColor = alpha(baseColor, rangeOpacity); Color handleRingsColor = radiusRingColor; - Color textColor = alpha(Color.WHITE, mainOpacity); + Color textColor = Color.WHITE; + Stroke handleRingsStroke = thinDashedLine; if (isSelected) { handleRingsColor = radiusRingColor = rangeRingsColor = ORANGE; - textColor = Color.WHITE; + handleRingsStroke = thickDashedLine; } else if (isHovered) { radiusRingColor = Color.YELLOW; + handleRingsColor = Color.WHITE; + } else { + textColor = alpha(textColor, mainOpacity); } // Draw handle rings - drawRing(g, x, y, innerHandleRingDiameter, handleRingsColor, thinDashedLine); - drawRing(g, x, y, outerHandleRingDiameter, handleRingsColor, thinDashedLine); + drawRing(g, x, y, innerHandleRingDiameter, handleRingsColor, handleRingsStroke); + drawRing(g, x, y, outerHandleRingDiameter, handleRingsColor, handleRingsStroke); // Draw radius rings if (!hideRadiusRings) { @@ -203,19 +472,171 @@ public Dimension render(Graphics2D g) { } g.setColor(textColor); - if (!hideLabels || isSelected) { + if (!hideLabels) { String info = l.def.description; + if (showDuplicationInfo) { + int newlines = (counter++ % 5) + 1; + info += "\n".repeat(newlines); + info += counter + ": " + l.hash; + info += "\n".repeat(5 - newlines); + } if (isSelected && !hideInfo) { info += String.format("\nradius: %d", liveInfo ? l.radius : l.def.radius); info += String.format("\nstrength: %.1f", liveInfo ? l.strength : l.def.strength); + var color = ColorUtils.linearToSrgb(l.def.color); + info += String.format("\ncolor: [%.0f, %.0f, %.0f]", color[0] * 255, color[1] * 255, color[2] * 255); + // Technically negative Y is up, but invert this in the info shown + info += String.format( + "\norigin: [%d, %d%s, %d]", + l.origin[0], + -(l.origin[1] + l.def.height), + l.def.height == 0 ? "" : " + " + l.def.height, + l.origin[2] + ); + info += String.format("\noffset: [%d, %d, %d]", l.offset[0], -l.offset[1], l.offset[2]); + info += String.format("\norientation: %d", l.orientation); } drawAlignedString(g, info, x, y + 25, TextAlignment.CENTER_ON_COLONS); } } + if (!selections.isEmpty()) { + switch (action) { + case GRAB: + Light l = selections.get(0); + var lightOrigin = freezeMode == RELATIVE_TO_ORIGIN ? l.origin : originalLightPosition; + for (int i = 0; i < 3; i++) + point[i] = lightOrigin[i]; + point[3] = 1; + float[] origin = new float[4]; + Mat4.projectVec(origin, projectionMatrix, point); + if (point[3] <= 0) + break; + + if (numFrozenAxes > 0) { + Color[] axisColors = { + new Color(0xef738c), + new Color(0x9fd853), + new Color(0x75ace1), + }; + g.setStroke(thickLine); + + float[] stepAlongAxis = new float[4]; + for (int i = 0; i < 3; i++) { + if (!frozenAxes[i]) { + int stepSize = 1000; + point[i] += stepSize; + Mat4.projectVec(stepAlongAxis, projectionMatrix, point); + point[i] -= stepSize; + + g.setColor(axisColors[i]); + drawLineSpan(g, origin, stepAlongAxis); + } + } + } + + for (int i = 0; i < 3; i++) + point[i] = l.pos[i]; + point[3] = 1; + float[] pos = new float[4]; + Mat4.projectVec(pos, projectionMatrix, point); + if (point[3] <= 0) + break; + + g.setColor(Color.YELLOW); + drawLineSegment(g, origin, pos); + break; + case SCALE: + break; + } + } + return null; } + private void drawLineSegment(Graphics2D g, float[] a, float[] b) { + g.drawLine( + Math.round(a[0]), + Math.round(a[1]), + Math.round(b[0]), + Math.round(b[1]) + ); + } + + private void drawLineSpan(Graphics2D g, float[] a, float[] b) { + float[] v = new float[2]; + Vector.subtract(v, b, a); + if (v[0] == 0 && v[1] == 0) + return; + + float[] p = new float[2]; + System.arraycopy(a, 0, p, 0, 2); + + var clipBounds = g.getClipBounds(); + float[][] axisBounds = { + { 0, clipBounds.width }, + { 0, clipBounds.height } + }; + + final float INF = Float.POSITIVE_INFINITY; + final float EPS = 1f; + + // First intersection with an edge within the screen bounds + float t = INF; + int intersectedEdge = -1; + outer: + for (int axis = 0; axis < 2; axis++) { + if (v[axis] == 0) + continue; + for (int edge = 0; edge < 2; edge++) { + float d = (axisBounds[axis][edge] - p[axis]) / v[axis]; + int oppositeAxis = (axis + 1) % 2; + float[] bounds = axisBounds[oppositeAxis]; + float coord = p[oppositeAxis] + v[oppositeAxis] * d; + if (bounds[0] - EPS < coord && coord < bounds[1] + EPS) { + t = d; + intersectedEdge = axis * 2 + edge; + break outer; + } + } + } + if (t == INF) + return; + + // Move the point to the selected edge + for (int i = 0; i < 2; i++) + p[i] += v[i] * t; + + t = INF; + outer: + for (int axis = 0; axis < 2; axis++) { + if (v[axis] == 0) + continue; + for (int edge = 0; edge < 2; edge++) { + // Skip the edge we've already intersected with + if (axis * 2 + edge == intersectedEdge) + continue; + + float d = (axisBounds[axis][edge] - p[axis]) / v[axis]; + int oppositeAxis = (axis + 1) % 2; + float[] bounds = axisBounds[oppositeAxis]; + float coord = p[oppositeAxis] + v[oppositeAxis] * d; + if (bounds[0] - EPS < coord && coord < bounds[1] + EPS) { + t = d; + break outer; + } + } + } + if (t == INF) + return; + + int x1 = Math.round(p[0]); + int y1 = Math.round(p[1]); + int x2 = Math.round(p[0] + v[0] * t); + int y2 = Math.round(p[1] + v[1] * t); + g.drawLine(x1, y1, x2, y2); + } + private void fillCircle(Graphics2D g, int centerX, int centerY, int diameter, Color color) { int r = diameter / 2; g.setColor(color); @@ -346,25 +767,41 @@ private void drawAlignedString(Graphics g, String[] lines, int centerX, int topY } } - private void flipYZ(float[] xyz) { - float temp = xyz[1]; - xyz[1] = xyz[2]; - xyz[2] = temp; - } - - private float[] projectPoint(float[] m, float[] xyzw) { - flipYZ(xyzw); - float[] result = new float[4]; - Mat4.projectVec(result, m, xyzw); - return result; - } - private Color alpha(Color rgb, int alpha) { if (alpha == 255) return rgb; return new Color(rgb.getRed(), rgb.getGreen(), rgb.getBlue(), alpha); } + private boolean applyPendingChange() { + if (action == Action.SELECT || selections.isEmpty()) + return false; + + action = Action.SELECT; + return true; + } + + private boolean discardPendingChange() { + if (action == Action.SELECT) { + if (!selections.isEmpty()) + selections.clear(); + return false; + } + + if (selections.isEmpty()) + return false; + + if (action == Action.GRAB) { + // Reset the light back to its original offset + var l = selections.get(0); + l.alignment = originalLightAlignment; + System.arraycopy(originalLightOffset, 0, l.offset, 0, 3); + } + + action = Action.SELECT; + return true; + } + @Override public MouseEvent mouseClicked(MouseEvent e) { return e; @@ -372,16 +809,41 @@ public MouseEvent mouseClicked(MouseEvent e) { @Override public MouseEvent mousePressed(MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - if (hovered != null && selected != hovered) - e.consume(); - selected = hovered; + if (SwingUtilities.isMiddleMouseButton(e)) + isProbablyRotatingCamera = true; + + switch (action) { + case SELECT: + if (SwingUtilities.isLeftMouseButton(e) && e.isControlDown()) { + e.consume(); + + selections.clear(); + if (!hovers.isEmpty()) { + selections.add(hovers.get(0)); + } else { + action = Action.SELECT; + } + } + break; + case GRAB: + if (SwingUtilities.isLeftMouseButton(e)) { + if (applyPendingChange()) + e.consume(); + } else if (SwingUtilities.isRightMouseButton(e)) { + if (discardPendingChange()) + e.consume(); + } + break; + case SCALE: + break; } return e; } @Override public MouseEvent mouseReleased(MouseEvent e) { + if (SwingUtilities.isMiddleMouseButton(e)) + isProbablyRotatingCamera = false; return e; } @@ -410,44 +872,147 @@ public void keyTyped(KeyEvent e) {} @Override public void keyPressed(KeyEvent e) { - switch (e.getKeyCode()) { - case KeyEvent.VK_A: - hideAnimLights = !hideAnimLights; - break; - case KeyEvent.VK_B: - toggleBlackColor = !toggleBlackColor; - break; - case KeyEvent.VK_H: - hideInvisibleLights = !hideInvisibleLights; - break; - case KeyEvent.VK_I: - hideInfo = !hideInfo; - break; - case KeyEvent.VK_L: - hideLabels = !hideLabels; - break; - case KeyEvent.VK_R: - hideRadiusRings = !hideRadiusRings; - break; - case KeyEvent.VK_U: - liveInfo = !liveInfo; - break; - } - // if (e.isControlDown() && e.isShiftDown() && e.getKeyCode() == KeyCode.KC_S) { // // TODO: Save changes to JSON // // Every time the JSON is updated, either through the file system or exporting changes, // // create a checkpoint. Store all checkpoints in memory throughout the client session. // // Implement ctrl Z and ctrl shift Z to redo. Forget reverted checkpoints upon file change. // } -// -// // TODO: Implement grabbing and scaling like in Blender -// switch (e.getKeyCode()) { -// case KeyCode.KC_G: // grab -// break; -// case KeyCode.KC_S: // scale -// break; -// } + + // Interaction with selected object + if (!selections.isEmpty()) { + var l = selections.get(0); + + if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_C) { + String str = "\n \"offset\": [ " + l.offset[0] + ", " + -l.offset[1] + ", " + l.offset[2] + " ],"; + + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + StringSelection string = new StringSelection(str); + clipboard.setContents(string, null); + clientThread.invoke(() -> client.addChatMessage( + ChatMessageType.GAMEMESSAGE, + "117 HD", + ColorUtil.wrapWithColorTag("[117 HD] Copied offset (must remove alignment): " + str.trim(), Color.GREEN), + "117 HD" + )); + } + + if (action == Action.SELECT) { + switch (e.getKeyCode()) { + case KeyEvent.VK_G: + action = Action.GRAB; + Arrays.fill(mouseDelta, 0); + + originalLightAlignment = l.alignment; + System.arraycopy(l.offset, 0, originalLightOffset, 0, 3); + System.arraycopy(l.pos, 0, originalLightPosition, 0, 3); + + l.alignment = Alignment.CUSTOM; + for (int i = 0; i < 3; i++) + l.offset[i] = l.pos[i] - l.origin[i]; + System.arraycopy(l.offset, 0, currentLightOffset, 0, 3); + break; + case KeyEvent.VK_S: + action = Action.SCALE; + break; + } + } else if (action == Action.GRAB) { + int axis = -1; + boolean cycleFreezeMode = false; + switch (e.getKeyCode()) { + case KeyEvent.VK_X: + axis = 0; + break; + case KeyEvent.VK_Y: + axis = 1; + break; + case KeyEvent.VK_Z: + axis = 2; + break; + case KeyEvent.VK_G: + cycleFreezeMode = true; + break; + } + if (axis != -1) { + boolean invert = e.isShiftDown(); + boolean modified = false; + for (int i = 0; i < 3; i++) { + boolean shouldFreeze = i == axis == invert; + if (shouldFreeze != frozenAxes[i]) + modified = true; + frozenAxes[i] = shouldFreeze; + } + if (modified) { + // Reset current offset + if (freezeMode == 0) + freezeMode = 1; + System.arraycopy(originalLightOffset, 0, l.offset, 0, 3); + } else { + cycleFreezeMode = true; + } + } + if (cycleFreezeMode) { + // If the same combination is repeated, cycle through different modes + freezeMode++; + freezeMode %= 3; + } + } + + switch (e.getKeyCode()) { + case KeyEvent.VK_ESCAPE: + if (discardPendingChange()) + e.consume(); + break; + case KeyEvent.VK_ENTER: + if (applyPendingChange()) + e.consume(); + break; + case KeyEvent.VK_BACK_SPACE: + // Reset light back to its defined offset + l.alignment = l.def.alignment; + System.arraycopy(l.def.offset, 0, l.offset, 0, 3); + System.arraycopy(l.offset, 0, currentLightOffset, 0, 3); + System.arraycopy(l.offset, 0, originalLightOffset, 0, 3); + Arrays.fill(mouseDelta, 0); + break; + } + } + + // Toggles + if (e.isControlDown()) { + switch (e.getKeyCode()) { + case KeyEvent.VK_A: + hideAnimLights = !hideAnimLights; + break; + case KeyEvent.VK_B: + toggleBlackColor = !toggleBlackColor; + break; + case KeyEvent.VK_D: + showDuplicationInfo = !showDuplicationInfo; + break; + case KeyEvent.VK_H: + hideInvisibleLights = !hideInvisibleLights; + break; + case KeyEvent.VK_I: + hideInfo = !hideInfo; + break; + case KeyEvent.VK_L: + hideLabels = !hideLabels; + break; + case KeyEvent.VK_M: + followMouse = !followMouse; + break; + case KeyEvent.VK_O: + toggleOpacity = !toggleOpacity; + break; + case KeyEvent.VK_R: + hideRadiusRings = !hideRadiusRings; + break; + case KeyEvent.VK_U: + liveInfo = !liveInfo; + break; + } + } } @Override diff --git a/src/main/java/rs117/hd/overlays/TileInfoOverlay.java b/src/main/java/rs117/hd/overlays/TileInfoOverlay.java index 955a01486c..dabe279aa7 100644 --- a/src/main/java/rs117/hd/overlays/TileInfoOverlay.java +++ b/src/main/java/rs117/hd/overlays/TileInfoOverlay.java @@ -276,8 +276,13 @@ private Rectangle drawTileInfo(Graphics2D g, SceneContext sceneContext, Tile til lines.add("Scene point: " + tileX + ", " + tileY + ", " + tileZ); lines.add("World point: " + Arrays.toString(worldPos)); - lines.add("Region ID: " + HDUtils.worldToRegionID(worldPos)); lines.add("Height: " + scene.getTileHeights()[tileZ][tileExX][tileExY]); + lines.add(String.format( + "Region ID: %d (%d, %d)", + HDUtils.worldToRegionID(worldPos), + worldPos[0] >> 6, + worldPos[1] >> 6 + )); int overlayId = scene.getOverlayIds()[tileZ][tileExX][tileExY]; var overlay = tileOverrideManager.getOverrideBeforeReplacements(worldPos, OVERLAY_FLAG | overlayId); @@ -402,24 +407,22 @@ else if (model != null) } GameObject[] gameObjects = tile.getGameObjects(); - if (gameObjects.length > 0) { - int counter = 0; - for (GameObject gameObject : gameObjects) { - if (gameObject == null) - continue; - counter++; - int height = -1; - var renderable = gameObject.getRenderable(); - if (renderable != null) - height = renderable.getModelHeight(); - lines.add(String.format( - "%s: ID=%s ori=%d height=%d", - ModelHash.getTypeName(ModelHash.getType(gameObject.getHash())), - getIdAndImpostorId(gameObject, renderable), - gameObject.getModelOrientation(), - height - )); - } + for (GameObject gameObject : gameObjects) { + if (gameObject == null) + continue; + int height = -1; + var renderable = gameObject.getRenderable(); + if (renderable != null) + height = renderable.getModelHeight(); + + lines.add(String.format( + "%s: ID=%s preori=%d ori=%d height=%d", + ModelHash.getTypeName(ModelHash.getType(gameObject.getHash())), + getIdAndImpostorId(gameObject, renderable), + HDUtils.getBakedOrientation(gameObject.getConfig()), + gameObject.getModelOrientation(), + height + )); } for (int i = 0; i < lines.size(); i++) { diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index 62ef03c415..9ef9bdc72a 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -1,10 +1,11 @@ package rs117.hd.overlays; +import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; -import net.runelite.client.util.Text; @RequiredArgsConstructor public enum Timer { + DRAW_FRAME, DRAW_SCENE, DRAW_RENDERABLE, GET_MODEL, @@ -14,9 +15,11 @@ public enum Timer { MODEL_PUSHING, MODEL_PUSHING_VERTEX, MODEL_PUSHING_NORMAL, - MODEL_PUSHING_UV(false, "Model Pushing UV"), + MODEL_PUSHING_UV(false, "Model pushing UV"), UPDATE_ENVIRONMENT, UPDATE_LIGHTS, + IMPOSTOR_TRACKING, + REPLACE_FISHING_SPOTS, UPLOAD_GEOMETRY(true), UPLOAD_UI(true, "Upload UI"), COMPUTE(true), @@ -34,21 +37,26 @@ public enum Timer { Timer() { isGpuTimer = false; - name = Text.titleCase(this); + name = enumToName(name()); } Timer(boolean isGpuTimer) { this.isGpuTimer = isGpuTimer; - name = Text.titleCase(this); + name = enumToName(name()); } - Timer(String name) { + Timer(@Nonnull String name) { isGpuTimer = false; this.name = name; } + private static String enumToName(String name) { + name = name.replace('_', ' '); + return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase(); + } + @Override public String toString() { - return name == null ? name() : name; + return name; } } diff --git a/src/main/java/rs117/hd/scene/EnvironmentManager.java b/src/main/java/rs117/hd/scene/EnvironmentManager.java index b6edc2b73c..ffaeef0db1 100644 --- a/src/main/java/rs117/hd/scene/EnvironmentManager.java +++ b/src/main/java/rs117/hd/scene/EnvironmentManager.java @@ -284,6 +284,9 @@ private void changeEnvironment(Environment newEnvironment, boolean skipTransitio skipTransition = false; } + if (currentEnvironment.instantTransition || newEnvironment.instantTransition) + skipTransition = true; + log.debug("changing environment from {} to {} (instant: {})", currentEnvironment, newEnvironment, skipTransition); currentEnvironment = newEnvironment; transitionComplete = false; diff --git a/src/main/java/rs117/hd/scene/FishingSpotReplacer.java b/src/main/java/rs117/hd/scene/FishingSpotReplacer.java new file mode 100644 index 0000000000..4af9148d69 --- /dev/null +++ b/src/main/java/rs117/hd/scene/FishingSpotReplacer.java @@ -0,0 +1,152 @@ +package rs117.hd.scene; + +import com.google.common.collect.Sets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import javax.inject.Inject; +import net.runelite.api.*; +import net.runelite.api.events.*; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import rs117.hd.HdPluginConfig; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; +import rs117.hd.scene.model_overrides.ModelOverride; + +import static rs117.hd.utils.ColorUtils.hsl; + +public class FishingSpotReplacer { + private static final int FISHING_SPOT_MODEL_ID = 41238; + private static final int FISHING_SPOT_ANIMATION_ID = 10793; + private static final int LAVA_SPOT_MODEL_ID = 2331; + private static final int LAVA_SPOT_ANIMATION_ID = 525; + private static final int LAVA_SPOT_COLOR = hsl("#837574"); + + // @formatter:off + private static final Set FISHING_SPOT_IDS = Set.of(394, 635, 1506, 1507, 1508, 1509, 1510, 1511, 1512, 1513, 1514, 1515, 1516, 1517, 1518, 1519, 1520, 1521, 1522, 1523, 1524, 1525, 1526, 1527, 1528, 1529, 1530, 1531, 1532, 1533, 1534, 1535, 1536, 1542, 1544, 2146, 2653, 2654, 2655, 3317, 3417, 3418, 3419, 3657, 3913, 3914, 3915, 4079, 4080, 4081, 4082, 4316, 4476, 4477, 4710, 4711, 4712, 4713, 4714, 5233, 5234, 5820, 5821, 6731, 6825, 7155, 7199, 7200, 7323, 7459, 7460, 7461, 7462, 7463, 7464, 7465, 7466, 7467, 7468, 7469, 7470, 7946, 7947, 8524, 8525, 8526, 8527, 9171, 9172, 9173, 9174, 9478, 12267); + private static final Set LAVA_FISHING_SPOT_IDS = Set.of(4928); + // @formatter:on + private static final Set NPC_IDS = Sets.union(FISHING_SPOT_IDS, LAVA_FISHING_SPOT_IDS).immutableCopy(); + + @Inject + private Client client; + + @Inject + private EventBus eventBus; + + @Inject + private HdPluginConfig config; + + @Inject + private TileOverrideManager tileOverrideManager; + + @Inject + private FrameTimer frameTimer; + + private final Map npcIndexToModel = new HashMap<>(); + private Animation fishingSpotAnimation; + private Animation lavaFishingSpotAnimation; + + public void startUp() { + eventBus.register(this); + fishingSpotAnimation = client.loadAnimation(FISHING_SPOT_ANIMATION_ID); + lavaFishingSpotAnimation = client.loadAnimation(LAVA_SPOT_ANIMATION_ID); + } + + public void shutDown() { + eventBus.unregister(this); + despawnRuneLiteObjects(); + fishingSpotAnimation = null; + lavaFishingSpotAnimation = null; + } + + public void despawnRuneLiteObjects() { + npcIndexToModel.values().forEach(rlobj -> rlobj.setActive(false)); + npcIndexToModel.clear(); + } + + public ModelOverride getModelOverride() { + if (!config.replaceFishingSpots()) + return null; + + ModelOverride override = new ModelOverride(); + override.hide = true; + override.npcIds = NPC_IDS; + return override; + } + + public void update() { + if (!config.replaceFishingSpots()) + return; + + frameTimer.begin(Timer.REPLACE_FISHING_SPOTS); + + // Despawn fishing spots for inactive NPCs + Set npcIndices = client.getNpcs().stream().map(NPC::getIndex).collect(Collectors.toSet()); + npcIndexToModel.entrySet().removeIf(entry -> { + if (npcIndices.contains(entry.getKey())) + return false; + entry.getValue().setActive(false); + return true; + }); + + client.getNpcs().forEach(this::spawnFishingSpot); + + // Update the location of active fishing spots to match their corresponding NPC's current position + npcIndexToModel.forEach((index, runeLiteObject) -> { + NPC npc = client.getCachedNPCs()[index]; + if (npc != null) + runeLiteObject.setLocation(npc.getLocalLocation(), client.getPlane()); + }); + + frameTimer.end(Timer.REPLACE_FISHING_SPOTS); + } + + @Subscribe + public void onNpcSpawned(NpcSpawned npcSpawned) { + spawnFishingSpot(npcSpawned.getNpc()); + } + + public void spawnFishingSpot(NPC npc) { + if (!NPC_IDS.contains(npc.getId())) + return; + + npcIndexToModel.computeIfAbsent(npc.getIndex(), i -> { + int modelId = FISHING_SPOT_MODEL_ID; + Animation animation = fishingSpotAnimation; + int recolor = -1; + + if (LAVA_FISHING_SPOT_IDS.contains(npc.getId())) { + modelId = LAVA_SPOT_MODEL_ID; + animation = lavaFishingSpotAnimation; + recolor = LAVA_SPOT_COLOR; + } else { + var lp = npc.getLocalLocation(); + if (lp.isInScene()) { + Tile tile = client.getScene().getTiles()[client.getPlane()][lp.getSceneX()][lp.getSceneY()]; + recolor = tileOverrideManager.getOverride(client.getScene(), tile).waterType.fishingSpotRecolor; + } + } + + ModelData modelData = client.loadModelData(modelId); + if (modelData == null) + return null; + + if (recolor != -1) { + modelData = modelData.cloneColors(); + Arrays.fill(modelData.getFaceColors(), (short) recolor); + } + + RuneLiteObject fishingSpot = client.createRuneLiteObject(); + fishingSpot.setAnimation(animation); + fishingSpot.setDrawFrontTilesFirst(false); + fishingSpot.setActive(true); + fishingSpot.setShouldLoop(true); + fishingSpot.setModel(modelData.light()); + return fishingSpot; + }); + } +} diff --git a/src/main/java/rs117/hd/scene/LightManager.java b/src/main/java/rs117/hd/scene/LightManager.java index 6d5fbff73e..90edb2f995 100644 --- a/src/main/java/rs117/hd/scene/LightManager.java +++ b/src/main/java/rs117/hd/scene/LightManager.java @@ -30,9 +30,8 @@ import com.google.gson.Gson; import java.io.IOException; import java.util.ArrayList; -import java.util.Comparator; -import java.util.Iterator; -import java.util.Optional; +import java.util.HashSet; +import java.util.List; import java.util.function.Predicate; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -51,10 +50,13 @@ import rs117.hd.HdPlugin; import rs117.hd.HdPluginConfig; import rs117.hd.config.MaxDynamicLights; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; import rs117.hd.scene.lights.Alignment; import rs117.hd.scene.lights.Light; import rs117.hd.scene.lights.LightDefinition; import rs117.hd.scene.lights.LightType; +import rs117.hd.scene.lights.TileObjectImpostorTracker; import rs117.hd.utils.HDUtils; import rs117.hd.utils.ModelHash; import rs117.hd.utils.Props; @@ -102,17 +104,19 @@ public class LightManager { @Inject private EntityHiderPlugin entityHiderPlugin; - public final ArrayList WORLD_LIGHTS = new ArrayList<>(); - public final ListMultimap NPC_LIGHTS = ArrayListMultimap.create(); - public final ListMultimap OBJECT_LIGHTS = ArrayListMultimap.create(); - public final ListMultimap PROJECTILE_LIGHTS = ArrayListMultimap.create(); - public final ListMultimap GRAPHICS_OBJECT_LIGHTS = ArrayListMultimap.create(); + @Inject + private FrameTimer frameTimer; - boolean configChanged = false; + private final ArrayList WORLD_LIGHTS = new ArrayList<>(); + private final ListMultimap NPC_LIGHTS = ArrayListMultimap.create(); + private final ListMultimap OBJECT_LIGHTS = ArrayListMultimap.create(); + private final ListMultimap PROJECTILE_LIGHTS = ArrayListMultimap.create(); + private final ListMultimap SPOT_ANIM_LIGHTS = ArrayListMultimap.create(); + private boolean reloadLights; private EntityHiderConfig entityHiderConfig; - public void loadConfig(Gson gson, ResourcePath path, boolean firstRun) { + public void loadConfig(Gson gson, ResourcePath path) { try { LightDefinition[] lights; try { @@ -130,23 +134,27 @@ public void loadConfig(Gson gson, ResourcePath path, boolean firstRun) { NPC_LIGHTS.clear(); OBJECT_LIGHTS.clear(); PROJECTILE_LIGHTS.clear(); - GRAPHICS_OBJECT_LIGHTS.clear(); + SPOT_ANIM_LIGHTS.clear(); for (LightDefinition lightDef : lights) { lightDef.normalize(); if (lightDef.worldX != null && lightDef.worldY != null) { Light light = new Light(lightDef); light.worldPoint = new WorldPoint(lightDef.worldX, lightDef.worldY, lightDef.plane); + light.persistent = true; WORLD_LIGHTS.add(light); } lightDef.npcIds.forEach(id -> NPC_LIGHTS.put(id, lightDef)); lightDef.objectIds.forEach(id -> OBJECT_LIGHTS.put(id, lightDef)); lightDef.projectileIds.forEach(id -> PROJECTILE_LIGHTS.put(id, lightDef)); - lightDef.graphicsObjectIds.forEach(id -> GRAPHICS_OBJECT_LIGHTS.put(id, lightDef)); + lightDef.spotAnimIds.forEach(id -> SPOT_ANIM_LIGHTS.put(id, lightDef)); } log.debug("Loaded {} lights", lights.length); - configChanged = !firstRun; + + // Reload lights once on plugin startup, and whenever lights.json should be hot-swapped. + // If we don't reload on startup, NPCs won't have lights added until RuneLite fires events + reloadLights = true; } catch (Exception ex) { log.error("Failed to parse light configuration", ex); } @@ -154,7 +162,7 @@ public void loadConfig(Gson gson, ResourcePath path, boolean firstRun) { public void startUp() { entityHiderConfig = configManager.getConfig(EntityHiderConfig.class); - LIGHTS_PATH.watch((path, first) -> loadConfig(plugin.getGson(), path, first)); + LIGHTS_PATH.watch(path -> loadConfig(plugin.getGson(), path)); eventBus.register(this); } @@ -162,152 +170,309 @@ public void shutDown() { eventBus.unregister(this); } - public void update(SceneContext sceneContext) { + public void update(@Nonnull SceneContext sceneContext) { assert client.isClientThread(); - if (client.getGameState() != GameState.LOGGED_IN || config.maxDynamicLights() == MaxDynamicLights.NONE) + if (client.getGameState() != GameState.LOGGED_IN || config.maxDynamicLights() == MaxDynamicLights.NONE) { + sceneContext.numVisibleLights = 0; return; + } - if (configChanged) { - configChanged = false; + if (reloadLights) { + reloadLights = false; loadSceneLights(sceneContext, null); - // check the NPCs in the scene to make sure they have lights assigned, if applicable, - // for scenarios in which HD mode or dynamic lights were disabled during NPC spawn - client.getNpcs().forEach(this::addNpcLights); + client.getNpcs().forEach(npc -> { + addNpcLights(npc); + addSpotAnimLights(npc); + }); } + // These should never occur, but just in case... + if (sceneContext.knownProjectiles.size() > 10000) { + log.warn("Too many projectiles tracked: {}. Clearing...", sceneContext.knownProjectiles.size()); + sceneContext.knownProjectiles.clear(); + } + if (sceneContext.lights.size() > 10000) { + log.warn("Too many lights: {}. Clearing...", sceneContext.lights.size()); + sceneContext.lights.clear(); + } + + int drawDistance = plugin.getDrawDistance() * LOCAL_TILE_SIZE; Tile[][][] tiles = sceneContext.scene.getExtendedTiles(); int[][][] tileHeights = sceneContext.scene.getTileHeights(); + var cachedNpcs = client.getCachedNPCs(); + var cachedPlayers = client.getCachedPlayers(); for (Light light : sceneContext.lights) { - light.distanceSquared = Integer.MAX_VALUE; - - light.elapsedTime += plugin.deltaClientTime; - if (light.elapsedTime < light.spawnDelay) { - light.visible = false; - continue; - } - - if (light.def.fixedDespawnTime && light.elapsedTime >= light.spawnDelay + light.despawnDelay) - light.markedForRemoval = true; - - if (light.object != null) { - light.visible = true; - if (light.isImpostor) { - var def = client.getObjectDefinition(light.object.getId()); - if (def.getImpostorIds() != null) { - // Only show the light if the impostor is currently active - var impostor = def.getImpostor(); - light.visible = impostor != null && impostor.getId() == light.objectId; + // Ways lights may get deleted: + // - animation-specific: + // effectively spawn when the animation they're attached to starts playing, and despawns when it stops, + // but they are typically replayable, so they don't fully despawn until marked for removal by something else + // - spotanim & projectile lights: + // automatically marked for removal upon completion + // - actor lights: + // may be automatically marked for removal if the actor becomes invalid + // - other lights: + // despawn when marked for removal by a RuneLite despawn event + // - fixed lifetime && !replayable: + // All non-replayable lights with a fixed lifetime will be automatically marked for removal when done playing + + // Light fade-in and fade-out are based on whether the parent currently exists + // Additionally, lights have an overruling fade-out when being deprioritized + + // Whatever the light is attached to is presumed to exist if it's not marked for removal yet + boolean parentExists = !light.markedForRemoval; + boolean hiddenTemporarily = false; + light.orientation = 0; + + if (light.tileObject != null) { + if (!light.markedForRemoval && light.animationSpecific && light.tileObject instanceof GameObject) { + int animationId = -1; + var renderable = ((GameObject) light.tileObject).getRenderable(); + if (renderable instanceof DynamicObject) { + var anim = ((DynamicObject) renderable).getAnimation(); + if (anim != null) + animationId = anim.getId(); } + parentExists = light.def.animationIds.contains(animationId); } } else if (light.projectile != null) { + light.origin[0] = (int) light.projectile.getX(); + light.origin[1] = (int) light.projectile.getZ() - light.def.height; + light.origin[2] = (int) light.projectile.getY(); if (light.projectile.getRemainingCycles() <= 0) { light.markedForRemoval = true; } else { - light.x = (int) light.projectile.getX(); - light.y = (int) light.projectile.getY(); - light.z = (int) light.projectile.getZ() - light.def.height; - light.visible = projectileLightVisible(); + hiddenTemporarily = !shouldShowProjectileLights(); + if (light.animationSpecific) { + var animation = light.projectile.getAnimation(); + parentExists = animation != null && light.def.animationIds.contains(animation.getId()); + } + light.orientation = (int) Math.round( + Math.atan2(light.projectile.getVelocityZ(), light.projectile.getVelocityX()) / UNIT); } } else if (light.graphicsObject != null) { + light.origin[0] = light.graphicsObject.getLocation().getX(); + light.origin[1] = light.graphicsObject.getZ() - light.def.height; + light.origin[2] = light.graphicsObject.getLocation().getY(); if (light.graphicsObject.finished()) { light.markedForRemoval = true; - } else { - light.x = light.graphicsObject.getLocation().getX(); - light.y = light.graphicsObject.getLocation().getY(); - light.z = light.graphicsObject.getZ() - light.def.height; - light.visible = true; + } else if (light.animationSpecific) { + var animation = light.graphicsObject.getAnimation(); + parentExists = animation != null && light.def.animationIds.contains(animation.getId()); } - } else if (light.actor != null) { - if (light.actor instanceof NPC && light.actor != client.getCachedNPCs()[((NPC) light.actor).getIndex()] || - light.actor instanceof Player && light.actor != client.getCachedPlayers()[((Player) light.actor).getId()] + } else if (light.actor != null && !light.markedForRemoval) { + if (light.actor instanceof NPC && light.actor != cachedNpcs[((NPC) light.actor).getIndex()] || + light.actor instanceof Player && light.actor != cachedPlayers[((Player) light.actor).getId()] || + light.spotAnimId != -1 && !light.actor.hasSpotAnim(light.spotAnimId) ) { + parentExists = false; light.markedForRemoval = true; - continue; - } + } else { + var lp = light.actor.getLocalLocation(); + light.origin[0] = lp.getX(); + light.origin[2] = lp.getY(); + int plane = client.getPlane(); + light.plane = plane; + light.orientation = light.actor.getCurrentOrientation(); + + if (light.animationSpecific) + parentExists = light.def.animationIds.contains(light.actor.getAnimation()); + + int tileExX = light.origin[0] / LOCAL_TILE_SIZE + SceneUploader.SCENE_OFFSET; + int tileExY = light.origin[2] / LOCAL_TILE_SIZE + SceneUploader.SCENE_OFFSET; + + // Some NPCs, such as Crystalline Hunllef in The Gauntlet, sometimes return scene X/Y values far outside the possible range. + Tile tile; + if (tileExX >= 0 && tileExY >= 0 && + tileExX < EXTENDED_SCENE_SIZE && tileExY < EXTENDED_SCENE_SIZE && + (tile = tiles[plane][tileExX][tileExY]) != null + ) { + // Check if the actor is hidden by another actor on the same tile + for (var gameObject : tile.getGameObjects()) { + if (gameObject == null || !(gameObject.getRenderable() instanceof Actor)) + continue; - var lp = light.actor.getLocalLocation(); - light.x = lp.getX(); - light.y = lp.getY(); - - // Offset the light's position based on its Alignment - if (light.def.alignment == Alignment.NORTH || - light.def.alignment == Alignment.NORTHEAST || - light.def.alignment == Alignment.NORTHWEST) - light.y += LOCAL_HALF_TILE_SIZE; - if (light.def.alignment == Alignment.SOUTH || - light.def.alignment == Alignment.SOUTHEAST || - light.def.alignment == Alignment.SOUTHWEST) - light.y -= LOCAL_HALF_TILE_SIZE; - if (light.def.alignment == Alignment.EAST || - light.def.alignment == Alignment.SOUTHEAST || - light.def.alignment == Alignment.NORTHEAST) - light.x += LOCAL_HALF_TILE_SIZE; - if (light.def.alignment == Alignment.WEST || - light.def.alignment == Alignment.SOUTHWEST || - light.def.alignment == Alignment.NORTHWEST) - light.x -= LOCAL_HALF_TILE_SIZE; - - int plane = client.getPlane(); - light.plane = plane; + // Assume only the first actor at the same exact location will be rendered + if (gameObject.getX() == light.origin[0] && gameObject.getY() == light.origin[2]) { + hiddenTemporarily = gameObject.getRenderable() != light.actor; + break; + } + } - // Some NPCs, such as Crystalline Hunllef in The Gauntlet, sometimes return scene X/Y values far outside the possible range. - int npcTileX = lp.getSceneX() + SceneUploader.SCENE_OFFSET; - int npcTileY = lp.getSceneY() + SceneUploader.SCENE_OFFSET; - if (npcTileX < 0 || npcTileY < 0 || npcTileX >= EXTENDED_SCENE_SIZE || npcTileY >= EXTENDED_SCENE_SIZE) { - light.visible = false; - } else { - // Tile null check is to prevent oddities caused by - once again - Crystalline Hunllef. - // May also apply to other NPCs in instances. - if (tiles[plane][npcTileX][npcTileY] != null && tiles[plane][npcTileX][npcTileY].getBridge() != null) { - plane++; + if (!hiddenTemporarily) + hiddenTemporarily = !isActorLightVisible(light.actor); + + if (tileExX != light.prevTileX || tileExY != light.prevTileY) { + light.prevTileX = tileExX; + light.prevTileY = tileExY; + + // Tile null check is to prevent oddities caused by - once again - Crystalline Hunllef. + // May also apply to other NPCs in instances. + if (tile.getBridge() != null) + plane++; + + // Interpolate between tile heights based on specific scene coordinates + float lerpX = (light.origin[0] % LOCAL_TILE_SIZE) / (float) LOCAL_TILE_SIZE; + float lerpY = (light.origin[2] % LOCAL_TILE_SIZE) / (float) LOCAL_TILE_SIZE; + int baseTileX = + (int) Math.floor(light.origin[0] / (float) LOCAL_TILE_SIZE) + SceneUploader.SCENE_OFFSET; + int baseTileY = + (int) Math.floor(light.origin[2] / (float) LOCAL_TILE_SIZE) + SceneUploader.SCENE_OFFSET; + float heightNorth = HDUtils.lerp( + tileHeights[plane][baseTileX][baseTileY + 1], + tileHeights[plane][baseTileX + 1][baseTileY + 1], + lerpX + ); + float heightSouth = HDUtils.lerp( + tileHeights[plane][baseTileX][baseTileY], + tileHeights[plane][baseTileX + 1][baseTileY], + lerpX + ); + float tileHeight = HDUtils.lerp(heightSouth, heightNorth, lerpY); + light.origin[1] = (int) tileHeight - 1 - light.def.height; + } } + } + } + + light.pos[0] = light.origin[0]; + light.pos[1] = light.origin[1]; + light.pos[2] = light.origin[2]; + + if (light.alignment.relative) { + light.orientation += light.preOrientation; + light.orientation += light.alignment.orientation; + } else { + light.orientation = 0; + } + light.orientation = HDUtils.mod(light.orientation, 2048); + + if (light.alignment == Alignment.CUSTOM) { + // orientation 0 = south + int sin = SINE[light.orientation]; + int cos = COSINE[light.orientation]; + int x = light.offset[0]; + int z = light.offset[2]; + light.pos[0] += -cos * x - sin * z >> 16; + light.pos[1] += light.offset[1]; + light.pos[2] += -cos * z + sin * x >> 16; + } else { + int localSizeX = light.sizeX * LOCAL_TILE_SIZE; + int localSizeY = light.sizeY * LOCAL_TILE_SIZE; + + float radius = localSizeX / 2f; + if (!light.alignment.radial) + radius = (float) Math.sqrt(localSizeX * localSizeX + localSizeX * localSizeX) / 2; + + float sine = SINE[light.orientation] / 65536f; + float cosine = COSINE[light.orientation] / 65536f; + cosine /= (float) localSizeX / (float) localSizeY; + + int offsetX = (int) (radius * sine); + int offsetY = (int) (radius * cosine); + + light.pos[0] += offsetX; + light.pos[2] += offsetY; + } - // Interpolate between tile heights based on specific scene coordinates. - float lerpX = (light.x % LOCAL_TILE_SIZE) / (float) LOCAL_TILE_SIZE; - float lerpY = (light.y % LOCAL_TILE_SIZE) / (float) LOCAL_TILE_SIZE; - int baseTileX = (int) Math.floor(light.x / (float) LOCAL_TILE_SIZE) + SceneUploader.SCENE_OFFSET; - int baseTileY = (int) Math.floor(light.y / (float) LOCAL_TILE_SIZE) + SceneUploader.SCENE_OFFSET; - float heightNorth = HDUtils.lerp( - tileHeights[plane][baseTileX][baseTileY + 1], - tileHeights[plane][baseTileX + 1][baseTileY + 1], - lerpX - ); - float heightSouth = HDUtils.lerp( - tileHeights[plane][baseTileX][baseTileY], - tileHeights[plane][baseTileX + 1][baseTileY], - lerpX - ); - float tileHeight = HDUtils.lerp(heightSouth, heightNorth, lerpY); - light.z = (int) tileHeight - 1 - light.def.height; - - light.visible = actorLightVisible(light.actor); + // This is a little bit slow, so only update it when necessary + if (light.prevPlane != light.plane) { + light.prevPlane = light.plane; + light.belowFloor = false; + light.aboveFloor = false; + int tileExX = light.pos[0] / LOCAL_TILE_SIZE + SceneUploader.SCENE_OFFSET; + int tileExY = light.pos[2] / LOCAL_TILE_SIZE + SceneUploader.SCENE_OFFSET; + if (light.plane >= 0 && tileExX >= 0 && tileExY >= 0 && tileExX < EXTENDED_SCENE_SIZE && tileExY < EXTENDED_SCENE_SIZE) { + Tile tileAbove = light.plane < 3 ? tiles[light.plane + 1][tileExX][tileExY] : null; + if (tileAbove != null && (tileAbove.getSceneTilePaint() != null || tileAbove.getSceneTileModel() != null)) + light.belowFloor = true; + + Tile lightTile = tiles[light.plane][tileExX][tileExY]; + if (lightTile != null && (lightTile.getSceneTilePaint() != null || lightTile.getSceneTileModel() != null)) + light.aboveFloor = true; } } - if (light.visible && !light.def.animationIds.isEmpty()) { - light.visible = false; - if (light.actor != null) { - light.visible = light.def.animationIds.contains(light.actor.getAnimation()); - } else if (light.object instanceof GameObject) { - var renderable = ((GameObject) light.object).getRenderable(); - if (renderable instanceof DynamicObject) { - var animation = ((DynamicObject) renderable).getAnimation(); - light.visible = animation != null && light.def.animationIds.contains(animation.getId()); + if (!hiddenTemporarily && !light.def.visibleFromOtherPlanes) { + // Hide certain lights on planes lower than the player to prevent light 'leaking' through the floor + if (light.plane < client.getPlane() && light.belowFloor) + hiddenTemporarily = true; + // Hide any light that is above the current plane and is above a solid floor + if (light.plane > client.getPlane() && light.aboveFloor) + hiddenTemporarily = true; + } + + if (parentExists != light.parentExists) { + light.parentExists = parentExists; + if (parentExists) { + // Reset the light if it's replayable and the parent just spawned + if (light.replayable) { + light.elapsedTime = 0; + if (light.dynamicLifetime) + light.lifetime = -1; } - } else if (light.projectile != null) { - var animation = light.projectile.getAnimation(); - light.visible = animation != null && light.def.animationIds.contains(animation.getId()); - } else if (light.graphicsObject != null) { - var animation = light.graphicsObject.getAnimation(); - light.visible = animation != null && light.def.animationIds.contains(animation.getId()); + } else if (light.lifetime == -1) { + // Schedule despawning of the light if the parent just despawned, and the light isn't already scheduled to despawn + float minLifetime = light.spawnDelay + light.fadeInDuration; + light.lifetime = Math.max(minLifetime, light.elapsedTime) + light.despawnDelay; } } - if (light.spotAnimId != -1 && light.actor != null && !light.actor.hasSpotAnim(light.spotAnimId)) - light.markedForRemoval = true; + if (hiddenTemporarily != light.hiddenTemporarily) + light.toggleTemporaryVisibility(); + + light.elapsedTime += plugin.deltaClientTime; + + light.visible = light.spawnDelay < light.elapsedTime && (light.lifetime == -1 || light.elapsedTime < light.lifetime); + + // If the light is temporarily hidden, keep it visible only while fading out + if (light.visible && light.hiddenTemporarily) + light.visible = light.changedVisibilityAt != -1 && light.elapsedTime - light.changedVisibilityAt < Light.VISIBILITY_FADE; + + if (light.visible) { + // Hide lights which cannot possibly affect the visible scene + int distFromCamera = (int) Math.max( + Math.abs(plugin.cameraPosition[0] - light.pos[0]), + Math.abs(plugin.cameraPosition[2] - light.pos[2]) + ) - light.radius; + if (distFromCamera > drawDistance) + light.visible = false; + } + + if (light.visible) { + // Calculate the distance between the player and the light to determine which + // lights to display based on the 'max dynamic lights' config option + int distX = plugin.cameraFocalPoint[0] - light.pos[0]; + int distZ = plugin.cameraFocalPoint[1] - light.pos[2]; + light.distanceSquared = distX * distX + distZ * distZ + light.pos[1] * light.pos[1]; + } + } + + // Order visible lights first, and by distance. Leave hidden lights unordered at the end. + sceneContext.lights.sort((a, b) -> { + // -1 = move a left of b + if (a.visible && b.visible) + return a.distanceSquared - b.distanceSquared; + if (!a.visible && !b.visible) + return 0; + return a.visible ? -1 : 1; + }); + + // Count number of visible lights + sceneContext.numVisibleLights = 0; + for (Light light : sceneContext.lights) { + // Exit early once encountering the first invisible light, or the light limit is reached + if (!light.visible || sceneContext.numVisibleLights >= plugin.configMaxDynamicLights) + break; + + sceneContext.numVisibleLights++; + + // If the light was temporarily hidden, begin fading in + if (!light.withinViewingDistance && light.hiddenTemporarily) + light.toggleTemporaryVisibility(); + light.withinViewingDistance = true; if (light.def.type == LightType.FLICKER) { double t = TWO_PI * (mod(plugin.elapsedTime, 60) / 60 + light.randomOffset); @@ -343,67 +508,32 @@ public void update(SceneContext sceneContext) { light.color = light.def.color; } + // Spawn & despawn fade-in and fade-out if (light.fadeInDuration > 0) - light.strength *= Math.min(1, (light.elapsedTime - light.spawnDelay) / light.fadeInDuration); - - // Calculate the distance between the player and the light to determine which - // lights to display based on the 'max dynamic lights' config option - int distX = plugin.cameraFocalPoint[0] - light.x; - int distY = plugin.cameraFocalPoint[1] - light.y; - light.distanceSquared = distX * distX + distY * distY + light.z * light.z; - - int tileX = (int) Math.floor(light.x / 128f) + SceneUploader.SCENE_OFFSET; - int tileY = (int) Math.floor(light.y / 128f) + SceneUploader.SCENE_OFFSET; + light.strength *= HDUtils.clamp((light.elapsedTime - light.spawnDelay) / light.fadeInDuration, 0, 1); + if (light.fadeOutDuration > 0 && light.lifetime != -1) + light.strength *= HDUtils.clamp((light.lifetime - light.elapsedTime) / light.fadeOutDuration, 0, 1); - light.belowFloor = false; - light.aboveFloor = false; - - if (tileX < EXTENDED_SCENE_SIZE && tileY < EXTENDED_SCENE_SIZE && tileX >= 0 && tileY >= 0 && light.plane >= 0) { - Tile aboveTile = light.plane < 3 ? tiles[light.plane + 1][tileX][tileY] : null; - - if (aboveTile != null && (aboveTile.getSceneTilePaint() != null || aboveTile.getSceneTileModel() != null)) { - light.belowFloor = true; - } - - Tile lightTile = tiles[light.plane][tileX][tileY]; - - if (lightTile != null && (lightTile.getSceneTilePaint() != null || lightTile.getSceneTileModel() != null)) { - light.aboveFloor = true; - } - } + light.applyTemporaryVisibilityFade(); } - Iterator lightIterator = sceneContext.lights.iterator(); - while (lightIterator.hasNext()) { - var light = lightIterator.next(); - if (!light.markedForRemoval) - continue; - - // If the light's despawn time isn't fixed, calculate when it should finish despawning - if (light.scheduledDespawnTime == -1) { - float minFadeTime = light.def.fadeOverlap ? - Math.max(light.fadeInDuration, light.fadeOutDuration) : - light.fadeInDuration + light.fadeOutDuration; - float minLifetime = light.spawnDelay + minFadeTime; - float lifetime = Math.max(minLifetime, light.elapsedTime); - light.scheduledDespawnTime = lifetime + Math.max(light.despawnDelay, light.fadeOutDuration); - } + for (int i = sceneContext.lights.size() - 1; i >= sceneContext.numVisibleLights; i--) { + Light light = sceneContext.lights.get(i); + light.withinViewingDistance = false; - float timeUntilDespawn = light.scheduledDespawnTime - light.elapsedTime; - if (light.fadeOutDuration > 0) - light.strength *= Math.min(1, timeUntilDespawn / light.fadeOutDuration); + // Automatically despawn non-replayable fixed lifetime lights when they expire + if (!light.replayable && light.lifetime != -1 && light.lifetime < light.elapsedTime) + light.markedForRemoval = true; - // Despawn the light - if (timeUntilDespawn <= 0) { - sceneContext.projectiles.remove(light.projectile); - lightIterator.remove(); + if (light.markedForRemoval) { + sceneContext.lights.remove(i); + if (light.projectile != null && --light.projectileRefCounter[0] == 0) + sceneContext.knownProjectiles.remove(light.projectile); } } - - sceneContext.lights.sort(Comparator.comparingInt(light -> light.distanceSquared)); } - private boolean actorLightVisible(@Nonnull Actor actor) { + private boolean isActorLightVisible(@Nonnull Actor actor) { try { // getModel may throw an exception from vanilla client code if (actor.getModel() == null) @@ -460,156 +590,81 @@ private boolean actorLightVisible(@Nonnull Actor actor) { return true; } - private boolean projectileLightVisible() - { - if (pluginManager.isPluginEnabled(entityHiderPlugin)) - { - if (entityHiderConfig.hideProjectiles()) - { - return false; - } - } - - return plugin.configProjectileLights; + private boolean shouldShowProjectileLights() { + return plugin.configProjectileLights && !(pluginManager.isPluginEnabled(entityHiderPlugin) && entityHiderConfig.hideProjectiles()); } public void loadSceneLights(SceneContext sceneContext, @Nullable SceneContext oldSceneContext) { assert client.isClientThread(); - // Copy over NPC and projectile lights from the old scene - ArrayList lightsToKeep = new ArrayList<>(); - if (oldSceneContext != null) + if (oldSceneContext == null) { + sceneContext.lights.clear(); + sceneContext.trackedTileObjects.clear(); + sceneContext.trackedVarps.clear(); + sceneContext.trackedVarbits.clear(); + sceneContext.knownProjectiles.clear(); + } else { + // Copy over NPC and projectile lights from the old scene + ArrayList lightsToKeep = new ArrayList<>(); for (Light light : oldSceneContext.lights) if (light.actor != null || light.projectile != null) lightsToKeep.add(light); - sceneContext.lights.clear(); - sceneContext.lights.addAll(lightsToKeep); - sceneContext.projectiles.clear(); - for (var l : lightsToKeep) - if (l.projectile != null) - sceneContext.projectiles.add(l.projectile); + sceneContext.lights.addAll(lightsToKeep); + for (var light : lightsToKeep) + if (light.projectile != null && oldSceneContext.knownProjectiles.contains(light.projectile)) + sceneContext.knownProjectiles.add(light.projectile); + } - for (Light light : WORLD_LIGHTS) - { + for (Light light : WORLD_LIGHTS) { assert light.worldPoint != null; - if (sceneContext.regionIds.contains(light.worldPoint.getRegionID())) - { - sceneContext.lights.add(light); - updateWorldLightPosition(sceneContext, light); - } + int regionId = light.worldPoint.getRegionID(); + if (sceneContext.regionIds.contains(regionId)) + addWorldLight(sceneContext, light); } for (Tile[][] plane : sceneContext.scene.getExtendedTiles()) { for (Tile[] column : plane) { for (Tile tile : column) { - if (tile == null) { + if (tile == null) continue; - } DecorativeObject decorativeObject = tile.getDecorativeObject(); - if (decorativeObject != null && decorativeObject.getRenderable() != null) { - addObjectLight(sceneContext, decorativeObject, tile.getRenderLevel()); - } + if (decorativeObject != null) + handleObjectSpawn(sceneContext, decorativeObject); WallObject wallObject = tile.getWallObject(); - if (wallObject != null && wallObject.getRenderable1() != null) { - int orientation = HDUtils.convertWallObjectOrientation(wallObject.getOrientationA()); - addObjectLight(sceneContext, wallObject, tile.getRenderLevel(), 1, 1, orientation); - } + if (wallObject != null) + handleObjectSpawn(sceneContext, wallObject); GroundObject groundObject = tile.getGroundObject(); - if (groundObject != null && groundObject.getRenderable() != null) { - addObjectLight(sceneContext, groundObject, tile.getRenderLevel()); - } + if (groundObject != null && groundObject.getRenderable() != null) + handleObjectSpawn(sceneContext, groundObject); for (GameObject gameObject : tile.getGameObjects()) { - if (gameObject != null) { - // Skip players & NPCs - if (gameObject.getRenderable() instanceof Actor) - continue; + // Skip nulls, players & NPCs + if (gameObject == null || gameObject.getRenderable() instanceof Actor) + continue; - addObjectLight( - sceneContext, - gameObject, - tile.getRenderLevel(), - gameObject.sizeX(), - gameObject.sizeY(), - gameObject.getOrientation()); - } + handleObjectSpawn(sceneContext, gameObject); } } } } } - public ArrayList getVisibleLights(int maxLights) { - SceneContext sceneContext = plugin.getSceneContext(); - ArrayList visibleLights = new ArrayList<>(); - - if (sceneContext == null) - return visibleLights; - - int maxDistanceSquared = plugin.getDrawDistance() * LOCAL_TILE_SIZE; - maxDistanceSquared *= maxDistanceSquared; - - for (Light light : sceneContext.lights) { - if (light.distanceSquared > maxDistanceSquared) - break; - - if (!light.visible) - continue; - - if (!light.def.visibleFromOtherPlanes) { - // Hide certain lights on planes lower than the player to prevent light 'leaking' through the floor - if (light.plane < client.getPlane() && light.belowFloor) - continue; - // Hide any light that is above the current plane and is above a solid floor - if (light.plane > client.getPlane() && light.aboveFloor) - continue; - } - - visibleLights.add(light); - - if (visibleLights.size() >= maxLights) - break; - } - - return visibleLights; - } - private void removeLightIf(Predicate predicate) { var sceneContext = plugin.getSceneContext(); - if (sceneContext != null) { - sceneContext.lights.forEach(light -> { - if (predicate.test(light)) - light.markedForRemoval = true; - }); - } - } - - @Subscribe - public void onProjectileMoved(ProjectileMoved projectileMoved) { - SceneContext sceneContext = plugin.getSceneContext(); if (sceneContext == null) return; + removeLightIf(sceneContext, predicate); + } - Projectile projectile = projectileMoved.getProjectile(); - if (!sceneContext.projectiles.add(projectile)) - return; - - for (LightDefinition lightDef : PROJECTILE_LIGHTS.get(projectile.getId())) { - Light light = new Light(lightDef); - light.projectile = projectile; - light.x = (int) projectile.getX(); - light.y = (int) projectile.getY(); - light.z = (int) projectile.getZ(); - light.plane = projectile.getFloor(); - light.visible = projectileLightVisible(); - - sceneContext.lights.add(light); - } + private void removeLightIf(@Nonnull SceneContext sceneContext, Predicate predicate) { + for (var light : sceneContext.lights) + if (predicate.test(light)) + light.markedForRemoval = true; } private void addSpotAnimLights(Actor actor) { @@ -618,21 +673,25 @@ private void addSpotAnimLights(Actor actor) { return; for (var spotAnim : actor.getSpotAnims()) { - for (var lightDef : GRAPHICS_OBJECT_LIGHTS.get(spotAnim.getId())) { - Light light = new Light(lightDef); + int spotAnimId = spotAnim.getId(); + for (var def : SPOT_ANIM_LIGHTS.get(spotAnim.getId())) { + boolean isDuplicate = sceneContext.lights.stream() + .anyMatch(light -> + light.spotAnimId == spotAnimId && + light.actor == actor && + light.def == def); + if (isDuplicate) + continue; + + Light light = new Light(def); light.plane = -1; - light.spotAnimId = spotAnim.getId(); + light.spotAnimId = spotAnimId; light.actor = actor; - light.visible = false; sceneContext.lights.add(light); } } } - public void removeSpotAnimLights(Actor actor) { - removeLightIf(light -> light.actor == actor); - } - private void addNpcLights(NPC npc) { var sceneContext = plugin.getSceneContext(); @@ -646,202 +705,285 @@ private void addNpcLights(NPC npc) if (modelOverride.hide) return; - for (LightDefinition lightDef : NPC_LIGHTS.get(npc.getId())) { + for (LightDefinition def : NPC_LIGHTS.get(npc.getId())) { // Prevent duplicate lights from being spawned for the same NPC boolean isDuplicate = sceneContext.lights.stream() - .anyMatch(light -> { - if (light.markedForRemoval) { - // Despawn it right away, since we're replacing it - light.visible = false; - light.scheduledDespawnTime = 0; - return false; - } - boolean sameLight = light.def == lightDef; - boolean sameNpc = light.actor == npc; - return sameLight && sameNpc; - }); + .anyMatch(light -> + light.actor == npc && + light.def == def); if (isDuplicate) continue; - Light light = new Light(lightDef); + Light light = new Light(def); light.plane = -1; light.actor = npc; - light.visible = false; sceneContext.lights.add(light); } - - addSpotAnimLights(npc); } - public void removeNpcLight(NPC npc) { - removeLightIf(light -> light.actor == npc); + private void handleObjectSpawn(TileObject object) { + var sceneContext = plugin.getSceneContext(); + if (sceneContext != null) + handleObjectSpawn(sceneContext, object); } - @Subscribe - public void onNpcSpawned(NpcSpawned npcSpawned) { - addNpcLights(npcSpawned.getNpc()); - } + private void handleObjectSpawn( + @Nonnull SceneContext sceneContext, + @Nonnull TileObject tileObject + ) { + if (sceneContext.trackedTileObjects.containsKey(tileObject)) + return; - @Subscribe - public void onNpcChanged(NpcChanged npcChanged) { - removeNpcLight(npcChanged.getNpc()); - addNpcLights(npcChanged.getNpc()); - } + var tracker = new TileObjectImpostorTracker(tileObject); + sceneContext.trackedTileObjects.put(tileObject, tracker); - @Subscribe - public void onNpcDespawned(NpcDespawned npcDespawned) - { - removeNpcLight(npcDespawned.getNpc()); - } + // prevent objects at plane -1 and below from having lights + if (tileObject.getPlane() < 0) + return; - @Subscribe - public void onPlayerSpawned(PlayerSpawned playerSpawned) { - addSpotAnimLights(playerSpawned.getPlayer()); - } + ObjectComposition def = client.getObjectDefinition(tileObject.getId()); + tracker.impostorIds = def.getImpostorIds(); + if (tracker.impostorIds != null) { + tracker.impostorVarbit = def.getVarbitId(); + tracker.impostorVarp = def.getVarPlayerId(); + if (tracker.impostorVarbit != -1) + sceneContext.trackedVarbits.put(tracker.impostorVarbit, tracker); + if (tracker.impostorVarp != -1) + sceneContext.trackedVarps.put(tracker.impostorVarp, tracker); + } - @Subscribe - public void onPlayerChanged(PlayerChanged playerChanged) { - // Don't add spotAnim lights on player change events, since it breaks death & respawn lights + trackImpostorChanges(sceneContext, tracker); } - @Subscribe - public void onGraphicChanged(GraphicChanged graphicChanged) { - var actor = graphicChanged.getActor(); - removeSpotAnimLights(actor); - addSpotAnimLights(actor); - } + private void handleObjectDespawn(TileObject tileObject) { + var sceneContext = plugin.getSceneContext(); + if (sceneContext == null) + return; - @Subscribe - public void onPlayerDespawned(PlayerDespawned playerDespawned) { - removeSpotAnimLights(playerDespawned.getPlayer()); - } + var tracker = sceneContext.trackedTileObjects.remove(tileObject); + if (tracker == null) + return; - private void addObjectLight(SceneContext sceneContext, TileObject tileObject, int plane) { - addObjectLight(sceneContext, tileObject, plane, 1, 1, -1); + if (tracker.spawnedAnyLights) { + long hash = tracker.lightHash(tracker.impostorId); + removeLightIf(sceneContext, l -> l.hash == hash); + } + + if (tracker.impostorVarbit != -1) + sceneContext.trackedVarbits.remove(tracker.impostorVarbit, tracker); + if (tracker.impostorVarp != -1) + sceneContext.trackedVarps.remove(tracker.impostorVarp, tracker); } - private void addObjectLight(SceneContext sceneContext, TileObject tileObject, int plane, int sizeX, int sizeY, int orientation) { - int id = tileObject.getId(); - if (tileObject instanceof GameObject) { - var def = client.getObjectDefinition(id); - if (def.getImpostorIds() != null) { - // Add a light for every possible impostor, and make the currently active one visible - for (int impostorId : def.getImpostorIds()) - addObjectLight(sceneContext, tileObject, impostorId, true, plane, sizeX, sizeY, orientation); - return; + private void trackImpostorChanges(@Nonnull SceneContext sceneContext, TileObjectImpostorTracker tracker) { + int impostorId = -1; + if (tracker.impostorIds != null) { + int impostorIndex = -1; + if (tracker.impostorVarbit != -1) { + impostorIndex = client.getVarbitValue(tracker.impostorVarbit); + } else if (tracker.impostorVarp != -1) { + impostorIndex = client.getVarpValue(tracker.impostorVarp); } + if (impostorIndex >= 0) + impostorId = tracker.impostorIds[Math.min(impostorIndex, tracker.impostorIds.length - 1)]; } - addObjectLight(sceneContext, tileObject, tileObject.getId(), false, plane, sizeX, sizeY, orientation); - } + // Don't do anything if the impostor is the same, unless the object just spawned + if (impostorId == tracker.impostorId && !tracker.justSpawned) + return; - private void addObjectLight( - SceneContext sceneContext, - TileObject tileObject, - int objectId, - boolean isImpostor, - int plane, - int sizeX, - int sizeY, - int orientation - ) { - for (LightDefinition lightDef : OBJECT_LIGHTS.get(objectId)) { - // prevent objects at plane -1 and below from having lights - if (tileObject.getPlane() <= -1) - continue; + int sizeX = 1; + int sizeY = 1; + Renderable[] renderables = new Renderable[2]; + int[] orientations = new int[2]; + + var tileObject = tracker.tileObject; + if (tileObject instanceof GroundObject) { + var object = (GroundObject) tileObject; + renderables[0] = object.getRenderable(); + orientations[0] = HDUtils.getBakedOrientation(object.getConfig()); + } else if (tileObject instanceof DecorativeObject) { + var object = (DecorativeObject) tileObject; + renderables[0] = object.getRenderable(); + renderables[1] = object.getRenderable2(); + orientations[0] = orientations[1] = HDUtils.getBakedOrientation(object.getConfig()); + } else if (tileObject instanceof WallObject) { + var object = (WallObject) tileObject; + renderables[0] = object.getRenderable1(); + renderables[1] = object.getRenderable2(); + orientations[0] = HDUtils.convertWallObjectOrientation(object.getOrientationA()); + orientations[1] = HDUtils.convertWallObjectOrientation(object.getOrientationB()); + } else if (tileObject instanceof GameObject) { + var object = (GameObject) tileObject; + sizeX = object.sizeX(); + sizeY = object.sizeY(); + renderables[0] = object.getRenderable(); + orientations[0] = HDUtils.getBakedOrientation(object.getConfig()); + } else { + log.warn("Unhandled TileObject type: id: {}, hash: {}", tileObject.getId(), tileObject.getHash()); + return; + } - // prevent duplicate lights from being spawned for the same object & impostor combination - long hash = tileObjectHash(tileObject); - boolean isDuplicate = sceneContext.lights.stream() - .anyMatch(light -> { - if (light.markedForRemoval) { - // Despawn it right away, since we're replacing it - light.visible = false; - light.scheduledDespawnTime = 0; - return false; - } - boolean sameObject = light.object == tileObject || hash == tileObjectHash(light.object); - boolean sameLight = light.def == lightDef; - boolean sameObjectId = light.objectId == objectId; - return sameObject && sameLight && sameObjectId; - }); - if (isDuplicate) + // Despawn old lights, if we spawned any for the previous impostor + if (tracker.spawnedAnyLights) { + long oldHash = tracker.lightHash(tracker.impostorId); + removeLightIf(sceneContext, l -> l.hash == oldHash); + tracker.spawnedAnyLights = false; + } + + long newHash = tracker.lightHash(impostorId); + List lights = OBJECT_LIGHTS.get(impostorId == -1 ? tileObject.getId() : impostorId); + HashSet onlySpawnOnce = new HashSet<>(); + + // Spawn animation-specific lights for each DynamicObject renderable, and non-animation-based lights + for (int i = 0; i < 2; i++) { + var renderable = renderables[i]; + if (renderable == null) continue; - int localPlane = tileObject.getPlane(); - Light light = new Light(lightDef); - light.plane = localPlane; - light.objectId = objectId; - if (isImpostor) { - light.isImpostor = true; - light.visible = false; + for (LightDefinition def : lights) { + // Rarely, it may be necessary to specify which of the two possible renderables the light should be attached to + if (def.renderableIndex == -1) { + // If unspecified, spawn it for the first non-null renderable + if (onlySpawnOnce.contains(def)) + continue; + onlySpawnOnce.add(def); + } else if (def.renderableIndex != i) { + continue; + } + + LocalPoint lp = tileObject.getLocalLocation(); + int lightX = lp.getX(); + int lightZ = lp.getY(); + int plane = tileObject.getPlane(); + + int tileExX = HDUtils.clamp(lp.getSceneX() + SceneUploader.SCENE_OFFSET, 0, EXTENDED_SCENE_SIZE - 2); + int tileExY = HDUtils.clamp(lp.getSceneY() + SceneUploader.SCENE_OFFSET, 0, EXTENDED_SCENE_SIZE - 2); + float lerpX = (lightX % LOCAL_TILE_SIZE) / (float) LOCAL_TILE_SIZE; + float lerpZ = (lightZ % LOCAL_TILE_SIZE) / (float) LOCAL_TILE_SIZE; + int tileZ = HDUtils.clamp(plane, 0, MAX_Z - 1); + + Tile[][][] tiles = sceneContext.scene.getExtendedTiles(); + Tile tile = tiles[tileZ][tileExX][tileExY]; + if (tile != null && tile.getBridge() != null && tileZ < MAX_Z - 1) + tileZ++; + + int[][][] tileHeights = sceneContext.scene.getTileHeights(); + float heightNorth = HDUtils.lerp( + tileHeights[tileZ][tileExX][tileExY + 1], + tileHeights[tileZ][tileExX + 1][tileExY + 1], + lerpX + ); + float heightSouth = HDUtils.lerp( + tileHeights[tileZ][tileExX][tileExY], + tileHeights[tileZ][tileExX + 1][tileExY], + lerpX + ); + float tileHeight = HDUtils.lerp(heightSouth, heightNorth, lerpZ); + + Light light = new Light(def); + light.hash = newHash; + light.tileObject = tileObject; + light.plane = plane; + light.preOrientation = orientations[i]; + light.origin[0] = lightX; + light.origin[1] = (int) tileHeight - light.def.height - 1; + light.origin[2] = lightZ; + light.sizeX = sizeX; + light.sizeY = sizeY; + sceneContext.lights.add(light); + tracker.spawnedAnyLights = true; } + } - LocalPoint localPoint = tileObject.getLocalLocation(); - int lightX = localPoint.getX(); - int lightY = localPoint.getY(); - int localSizeX = sizeX * LOCAL_TILE_SIZE; - int localSizeY = sizeY * LOCAL_TILE_SIZE; + tracker.impostorId = impostorId; + tracker.justSpawned = false; + } - if (orientation != -1 && light.def.alignment != Alignment.CENTER) { - float radius = localSizeX / 2f; - if (!light.def.alignment.radial) - radius = (float) Math.sqrt(localSizeX * localSizeX + localSizeX * localSizeX) / 2; + private void addWorldLight(SceneContext sceneContext, Light light) { + assert light.worldPoint != null; + var firstlp = sceneContext.worldInstanceToLocals(light.worldPoint).findFirst(); + if (firstlp.isEmpty()) + return; - if (!light.def.alignment.relative) - orientation = 0; - orientation += light.def.alignment.orientation; - orientation %= 2048; + LocalPoint lp = firstlp.get(); + int tileExX = lp.getSceneX() + SceneUploader.SCENE_OFFSET; + int tileExY = lp.getSceneY() + SceneUploader.SCENE_OFFSET; + if (tileExX < 0 || tileExY < 0 || tileExX >= EXTENDED_SCENE_SIZE || tileExY >= EXTENDED_SCENE_SIZE) + return; - float sine = SINE[orientation] / 65536f; - float cosine = COSINE[orientation] / 65536f; - cosine /= (float) localSizeX / (float) localSizeY; + light.origin[0] = lp.getX() + LOCAL_HALF_TILE_SIZE; + light.origin[1] = sceneContext.scene.getTileHeights()[light.plane][tileExX][tileExY] - light.def.height - 1; + light.origin[2] = lp.getY() + LOCAL_HALF_TILE_SIZE; + sceneContext.lights.add(light); + } - int offsetX = (int) (radius * sine); - int offsetY = (int) (radius * cosine); + @Subscribe + public void onProjectileMoved(ProjectileMoved projectileMoved) { + SceneContext sceneContext = plugin.getSceneContext(); + if (sceneContext == null) + return; - lightX += offsetX; - lightY += offsetY; - } + // Since there's no spawn & despawn events for projectiles, add when they move for the first time + Projectile projectile = projectileMoved.getProjectile(); + if (!sceneContext.knownProjectiles.add(projectile)) + return; - float tileX = (float) lightX / LOCAL_TILE_SIZE + SceneUploader.SCENE_OFFSET; - float tileY = (float) lightY / LOCAL_TILE_SIZE + SceneUploader.SCENE_OFFSET; - float lerpX = (lightX % LOCAL_TILE_SIZE) / (float) LOCAL_TILE_SIZE; - float lerpY = (lightY % LOCAL_TILE_SIZE) / (float) LOCAL_TILE_SIZE; - int tileMinX = (int) Math.floor(tileX); - int tileMinY = (int) Math.floor(tileY); - int tileMaxX = tileMinX + 1; - int tileMaxY = tileMinY + 1; - tileMinX = HDUtils.clamp(tileMinX, 0, EXTENDED_SCENE_SIZE - 1); - tileMinY = HDUtils.clamp(tileMinY, 0, EXTENDED_SCENE_SIZE - 1); - tileMaxX = HDUtils.clamp(tileMaxX, 0, EXTENDED_SCENE_SIZE - 1); - tileMaxY = HDUtils.clamp(tileMaxY, 0, EXTENDED_SCENE_SIZE - 1); - - int[][][] tileHeights = sceneContext.scene.getTileHeights(); - float heightNorth = HDUtils.lerp( - tileHeights[plane][tileMinX][tileMaxY], - tileHeights[plane][tileMaxX][tileMaxY], - lerpX - ); - float heightSouth = HDUtils.lerp( - tileHeights[plane][tileMinX][tileMinY], - tileHeights[plane][tileMaxX][tileMinY], - lerpX - ); - float tileHeight = HDUtils.lerp(heightSouth, heightNorth, lerpY); - - light.x = lightX; - light.y = lightY; - light.z = (int) tileHeight - light.def.height - 1; - light.object = tileObject; + int[] refCounter = { 0 }; + for (LightDefinition lightDef : PROJECTILE_LIGHTS.get(projectile.getId())) { + Light light = new Light(lightDef); + light.projectile = projectile; + light.projectileRefCounter = refCounter; + refCounter[0]++; + light.origin[0] = (int) projectile.getX(); + light.origin[1] = (int) projectile.getZ(); + light.origin[2] = (int) projectile.getY(); + light.plane = projectile.getFloor(); sceneContext.lights.add(light); } } - private void removeObjectLight(TileObject tileObject) - { - removeLightIf(light -> light.object == tileObject);// && light.plane == tileObject.getPlane()); + @Subscribe + public void onNpcSpawned(NpcSpawned spawn) { + NPC npc = spawn.getNpc(); + addNpcLights(npc); + addSpotAnimLights(npc); + } + + @Subscribe + public void onNpcChanged(NpcChanged change) { + // Respawn non-spotanim lights + NPC npc = change.getNpc(); + removeLightIf(light -> light.actor == npc && light.spotAnimId == -1); + addNpcLights(change.getNpc()); + } + + @Subscribe + public void onNpcDespawned(NpcDespawned despawn) { + NPC npc = despawn.getNpc(); + removeLightIf(light -> light.actor == npc); + } + + @Subscribe + public void onPlayerSpawned(PlayerSpawned spawn) { + addSpotAnimLights(spawn.getPlayer()); + } + + @Subscribe + public void onPlayerChanged(PlayerChanged change) { + // Don't add spotAnim lights on player change events, since it breaks death & respawn lights + } + + @Subscribe + public void onGraphicChanged(GraphicChanged change) { + addSpotAnimLights(change.getActor()); + } + + @Subscribe + public void onPlayerDespawned(PlayerDespawned despawn) { + Player player = despawn.getPlayer(); + removeLightIf(light -> light.actor == player); } @Subscribe @@ -851,124 +993,76 @@ public void onGraphicsObjectCreated(GraphicsObjectCreated graphicsObjectCreated) return; GraphicsObject graphicsObject = graphicsObjectCreated.getGraphicsObject(); - for (LightDefinition lightDef : GRAPHICS_OBJECT_LIGHTS.get(graphicsObject.getId())) { + for (LightDefinition lightDef : SPOT_ANIM_LIGHTS.get(graphicsObject.getId())) { Light light = new Light(lightDef); light.graphicsObject = graphicsObject; var lp = graphicsObject.getLocation(); - light.x = lp.getX(); - light.y = lp.getY(); - light.z = graphicsObject.getZ(); + light.origin[0] = lp.getX(); + light.origin[1] = graphicsObject.getZ(); + light.origin[2] = lp.getY(); light.plane = graphicsObject.getLevel(); - sceneContext.lights.add(light); } } - private long tileObjectHash(@Nullable TileObject tileObject) - { - if (tileObject == null) - return 0; - - LocalPoint local = tileObject.getLocalLocation(); - long hash = local.getX(); - hash = hash * 31 + local.getY(); - hash = hash * 31 + tileObject.getPlane(); - hash = hash * 31 + tileObject.getId(); - return hash; + @Subscribe + public void onGameObjectSpawned(GameObjectSpawned spawn) { + handleObjectSpawn(spawn.getGameObject()); } - private void updateWorldLightPosition(SceneContext sceneContext, Light light) - { - assert light.worldPoint != null; - - Optional firstLocalPoint = sceneContext.worldInstanceToLocals(light.worldPoint).stream().findFirst(); - if (firstLocalPoint.isEmpty()) { - return; - } - - LocalPoint local = firstLocalPoint.get(); - - light.x = local.getX() + LOCAL_HALF_TILE_SIZE; - light.y = local.getY() + LOCAL_HALF_TILE_SIZE; - int tileExX = local.getSceneX() + SceneUploader.SCENE_OFFSET; - int tileExY = local.getSceneY() + SceneUploader.SCENE_OFFSET; - if (tileExX >= 0 && tileExY >= 0 && tileExX < EXTENDED_SCENE_SIZE && tileExY < EXTENDED_SCENE_SIZE) { - light.z = sceneContext.scene.getTileHeights()[light.plane][tileExX][tileExY] - light.def.height - 1; - } - - if (light.def.alignment == Alignment.NORTH || light.def.alignment == Alignment.NORTHEAST - || light.def.alignment == Alignment.NORTHWEST) - light.y += LOCAL_HALF_TILE_SIZE; - if (light.def.alignment == Alignment.EAST || light.def.alignment == Alignment.NORTHEAST - || light.def.alignment == Alignment.SOUTHEAST) - light.x += LOCAL_HALF_TILE_SIZE; - if (light.def.alignment == Alignment.SOUTH || light.def.alignment == Alignment.SOUTHEAST - || light.def.alignment == Alignment.SOUTHWEST) - light.y -= LOCAL_HALF_TILE_SIZE; - if (light.def.alignment == Alignment.WEST || light.def.alignment == Alignment.NORTHWEST - || light.def.alignment == Alignment.SOUTHWEST) - light.x -= LOCAL_HALF_TILE_SIZE; + @Subscribe + public void onGameObjectDespawned(GameObjectDespawned despawn) { + handleObjectDespawn(despawn.getGameObject()); } @Subscribe - public void onGameObjectSpawned(GameObjectSpawned gameObjectSpawned) - { - SceneContext sceneContext = plugin.getSceneContext(); - if (sceneContext == null) - return; - GameObject gameObject = gameObjectSpawned.getGameObject(); - addObjectLight(sceneContext, gameObject, gameObjectSpawned.getTile().getRenderLevel(), gameObject.sizeX(), gameObject.sizeY(), gameObject.getOrientation()); + public void onWallObjectSpawned(WallObjectSpawned spawn) { + handleObjectSpawn(spawn.getWallObject()); } @Subscribe - public void onGameObjectDespawned(GameObjectDespawned gameObjectDespawned) - { - removeObjectLight(gameObjectDespawned.getGameObject()); + public void onWallObjectDespawned(WallObjectDespawned despawn) { + handleObjectDespawn(despawn.getWallObject()); } @Subscribe - public void onWallObjectSpawned(WallObjectSpawned wallObjectSpawned) - { - SceneContext sceneContext = plugin.getSceneContext(); - if (sceneContext == null) - return; - WallObject wallObject = wallObjectSpawned.getWallObject(); - addObjectLight(sceneContext, wallObject, wallObjectSpawned.getTile().getRenderLevel(), 1, 1, wallObject.getOrientationA()); + public void onDecorativeObjectSpawned(DecorativeObjectSpawned spawn) { + handleObjectSpawn(spawn.getDecorativeObject()); } @Subscribe - public void onWallObjectDespawned(WallObjectDespawned wallObjectDespawned) - { - removeObjectLight(wallObjectDespawned.getWallObject()); + public void onDecorativeObjectDespawned(DecorativeObjectDespawned despawn) { + handleObjectDespawn(despawn.getDecorativeObject()); } @Subscribe - public void onDecorativeObjectSpawned(DecorativeObjectSpawned decorativeObjectSpawned) - { - SceneContext sceneContext = plugin.getSceneContext(); - if (sceneContext == null) - return; - addObjectLight(sceneContext, decorativeObjectSpawned.getDecorativeObject(), decorativeObjectSpawned.getTile().getRenderLevel()); + public void onGroundObjectSpawned(GroundObjectSpawned spawn) { + handleObjectSpawn(spawn.getGroundObject()); } @Subscribe - public void onDecorativeObjectDespawned(DecorativeObjectDespawned decorativeObjectDespawned) - { - removeObjectLight(decorativeObjectDespawned.getDecorativeObject()); + public void onGroundObjectDespawned(GroundObjectDespawned despawn) { + handleObjectDespawn(despawn.getGroundObject()); } @Subscribe - public void onGroundObjectSpawned(GroundObjectSpawned groundObjectSpawned) - { - SceneContext sceneContext = plugin.getSceneContext(); + public void onVarbitChanged(VarbitChanged event) { + var sceneContext = plugin.getSceneContext(); if (sceneContext == null) return; - addObjectLight(sceneContext, groundObjectSpawned.getGroundObject(), groundObjectSpawned.getTile().getRenderLevel()); - } - @Subscribe - public void onGroundObjectDespawned(GroundObjectDespawned groundObjectDespawned) - { - removeObjectLight(groundObjectDespawned.getGroundObject()); + if (plugin.enableDetailedTimers) + frameTimer.begin(Timer.IMPOSTOR_TRACKING); + // Check if the event is specifically a varbit change first, + // since all varbit changes are necessarily also varp changes + if (event.getVarbitId() != -1) { + for (var tracker : sceneContext.trackedVarbits.get(event.getVarbitId())) + trackImpostorChanges(sceneContext, tracker); + } else if (event.getVarpId() != -1) { + for (var tracker : sceneContext.trackedVarps.get(event.getVarpId())) + trackImpostorChanges(sceneContext, tracker); + } + if (plugin.enableDetailedTimers) + frameTimer.end(Timer.IMPOSTOR_TRACKING); } } diff --git a/src/main/java/rs117/hd/scene/ModelOverrideManager.java b/src/main/java/rs117/hd/scene/ModelOverrideManager.java index 6b636e3b91..93f65e92b4 100644 --- a/src/main/java/rs117/hd/scene/ModelOverrideManager.java +++ b/src/main/java/rs117/hd/scene/ModelOverrideManager.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.Objects; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; @@ -39,6 +40,9 @@ public class ModelOverrideManager { @Inject private ModelPusher modelPusher; + @Inject + private FishingSpotReplacer fishingSpotReplacer; + private final HashMap modelOverrides = new HashMap<>(); private FileWatcher.UnregisterCallback fileWatcher; @@ -74,6 +78,8 @@ public void startUp() { log.error("Failed to load model overrides:", ex); } + addOverride(fishingSpotReplacer.getModelOverride()); + if (!first) { clientThread.invoke(() -> { modelPusher.clearModelCache(); @@ -97,8 +103,8 @@ public void reload() { startUp(); } - private void addOverride(ModelOverride override) { - if (override.seasonalTheme != null && override.seasonalTheme != plugin.configSeasonalTheme) + private void addOverride(@Nullable ModelOverride override) { + if (override == null || override.seasonalTheme != null && override.seasonalTheme != plugin.configSeasonalTheme) return; for (int id : override.npcIds) diff --git a/src/main/java/rs117/hd/scene/ProceduralGenerator.java b/src/main/java/rs117/hd/scene/ProceduralGenerator.java index abde616af8..9507b32c62 100644 --- a/src/main/java/rs117/hd/scene/ProceduralGenerator.java +++ b/src/main/java/rs117/hd/scene/ProceduralGenerator.java @@ -43,17 +43,17 @@ import static net.runelite.api.Perspective.*; import static rs117.hd.scene.SceneUploader.SCENE_OFFSET; import static rs117.hd.scene.tile_overrides.TileOverride.OVERLAY_FLAG; -import static rs117.hd.utils.HDUtils.add; import static rs117.hd.utils.HDUtils.calculateSurfaceNormals; import static rs117.hd.utils.HDUtils.clamp; import static rs117.hd.utils.HDUtils.dotLightDirectionTile; import static rs117.hd.utils.HDUtils.lerp; import static rs117.hd.utils.HDUtils.vertexHash; +import static rs117.hd.utils.Vector.add; @Slf4j @Singleton public class ProceduralGenerator { - public static final int[] DEPTH_LEVEL_SLOPE = new int[] { 150, 300, 470, 610, 700, 750, 820, 920, 1080, 1300, 1350, 1380 }; + public static final int[] DEPTH_LEVEL_SLOPE = new int[] {384, 666, 818, 1056, 1142, 1224, 1303, 1379, 1452, 1522, 1589, 1645, 1700, 1775, 1860, 1994, 2175, 2329, 2459, 2571, 2665, 2745, 2825, 2890, 2945, 2996, 3047, 3085, 3114, 3140, 3158, 3170, 3200}; public static final int VERTICES_PER_FACE = 3; public static final boolean[][] TILE_OVERLAY_TRIS = new boolean[][] @@ -607,18 +607,8 @@ else if (tile.getSceneTileModel() != null) { continue; } - int maxRange = DEPTH_LEVEL_SLOPE[sceneContext.underwaterDepthLevels[z][x][y] - 1]; - int minRange = (int) (DEPTH_LEVEL_SLOPE[sceneContext.underwaterDepthLevels[z][x][y] - 1] * 0.1f); - // Range from noise-generated terrain is 10-60. - // Translate the result from range 0-1. -// float noiseOffset = (HeightCalc.calculate(baseX + x + 0xe3b7b, baseY + y + 0x87cce) - 10) / 50f; - float noiseOffset = 0.5f; - // limit range of variation - float minOffset = 0.25f; - float maxOffset = 0.75f; - noiseOffset = lerp(minOffset, maxOffset, noiseOffset); - // apply offset to vertex height range - int heightOffset = (int) lerp(minRange, maxRange, noiseOffset); + int depth = DEPTH_LEVEL_SLOPE[sceneContext.underwaterDepthLevels[z][x][y] - 1]; + int heightOffset = (int) (depth * .55f); // legacy weirdness underwaterDepths[z][x][y] = heightOffset; } } diff --git a/src/main/java/rs117/hd/scene/SceneContext.java b/src/main/java/rs117/hd/scene/SceneContext.java index c6726c2e97..ce8e322ffe 100644 --- a/src/main/java/rs117/hd/scene/SceneContext.java +++ b/src/main/java/rs117/hd/scene/SceneContext.java @@ -1,12 +1,13 @@ package rs117.hd.scene; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; -import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; import net.runelite.api.*; @@ -15,6 +16,7 @@ import rs117.hd.data.materials.Material; import rs117.hd.scene.environments.Environment; import rs117.hd.scene.lights.Light; +import rs117.hd.scene.lights.TileObjectImpostorTracker; import rs117.hd.utils.AABB; import rs117.hd.utils.HDUtils; import rs117.hd.utils.buffer.GpuFloatBuffer; @@ -57,8 +59,12 @@ public class SceneContext { public Map vertexUnderwaterDepth; public int[][][] underwaterDepthLevels; + public int numVisibleLights = 0; public final ArrayList lights = new ArrayList<>(); - public final HashSet projectiles = new HashSet<>(); + public final HashSet knownProjectiles = new HashSet<>(); + public final HashMap trackedTileObjects = new HashMap<>(); + public final ListMultimap trackedVarps = ArrayListMultimap.create(); + public final ListMultimap trackedVarbits = ArrayListMultimap.create(); public final ArrayList environments = new ArrayList<>(); @@ -113,13 +119,11 @@ public synchronized void destroy() { stagingBufferNormals = null; } - public int getVertexOffset() - { + public int getVertexOffset() { return stagingBufferVertices.position() / VERTEX_SIZE; } - public int getUvOffset() - { + public int getUvOffset() { return stagingBufferUvs.position() / UV_SIZE; } @@ -128,21 +132,18 @@ public int getUvOffset() * If the {@link LocalPoint} is not in the scene, this returns untranslated coordinates when in instances. * * @param localPoint to transform - * @param plane which the local coordinate is on + * @param plane which the local coordinate is on * @return world coordinate */ - public int[] localToWorld(LocalPoint localPoint, int plane) - { + public int[] localToWorld(LocalPoint localPoint, int plane) { return HDUtils.localToWorld(scene, localPoint.getX(), localPoint.getY(), plane); } - public int[] localToWorld(int localX, int localY, int plane) - { + public int[] localToWorld(int localX, int localY, int plane) { return HDUtils.localToWorld(scene, localX, localY, plane); } - public int[] sceneToWorld(int sceneX, int sceneY, int plane) - { + public int[] sceneToWorld(int sceneX, int sceneY, int plane) { return HDUtils.localToWorld(scene, sceneX * LOCAL_TILE_SIZE, sceneY * LOCAL_TILE_SIZE, plane); } @@ -150,13 +151,12 @@ public int[] extendedSceneToWorld(int sceneExX, int sceneExY, int plane) { return sceneToWorld(sceneExX - SCENE_OFFSET, sceneExY - SCENE_OFFSET, plane); } - public Collection worldInstanceToLocals(WorldPoint worldPoint) + public Stream worldInstanceToLocals(WorldPoint worldPoint) { return WorldPoint.toLocalInstance(scene, worldPoint) .stream() .map(this::worldToLocal) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + .filter(Objects::nonNull); } /** @@ -188,4 +188,20 @@ public boolean intersects(Area area) { public boolean intersects(AABB... aabbs) { return HDUtils.sceneIntersects(scene, expandedMapLoadingChunks, aabbs); } + + public int getObjectConfig(int tileZ, int tileExX, int tileExY, long hash) { + Tile tile = scene.getExtendedTiles()[tileZ][tileExX][tileExY]; + if (tile == null) + return 0; + if (tile.getWallObject() != null && tile.getWallObject().getHash() == hash) + return tile.getWallObject().getConfig(); + if (tile.getDecorativeObject() != null && tile.getDecorativeObject().getHash() == hash) + return tile.getDecorativeObject().getConfig(); + if (tile.getGroundObject() != null && tile.getGroundObject().getHash() == hash) + return tile.getGroundObject().getConfig(); + for (GameObject gameObject : tile.getGameObjects()) + if (gameObject != null && gameObject.getHash() == hash) + return gameObject.getConfig(); + return 0; + } } diff --git a/src/main/java/rs117/hd/scene/SceneUploader.java b/src/main/java/rs117/hd/scene/SceneUploader.java index 941edb6c26..b0168da573 100644 --- a/src/main/java/rs117/hd/scene/SceneUploader.java +++ b/src/main/java/rs117/hd/scene/SceneUploader.java @@ -50,6 +50,7 @@ import static rs117.hd.HdPlugin.SCALAR_BYTES; import static rs117.hd.HdPlugin.UV_SIZE; import static rs117.hd.HdPlugin.VERTEX_SIZE; +import static rs117.hd.scene.tile_overrides.TileOverride.NONE; import static rs117.hd.scene.tile_overrides.TileOverride.OVERLAY_FLAG; import static rs117.hd.utils.HDUtils.clamp; @@ -362,9 +363,10 @@ private void upload(SceneContext sceneContext, @Nonnull Tile tile, int tileExX, DecorativeObject decorativeObject = tile.getDecorativeObject(); if (decorativeObject != null) { Renderable renderable = decorativeObject.getRenderable(); + int orientation = HDUtils.getBakedOrientation(decorativeObject.getConfig()); if (renderable instanceof Model) { uploadModel(sceneContext, tile, ModelHash.packUuid(ModelHash.TYPE_OBJECT, decorativeObject.getId()), (Model) renderable, - HDUtils.getBakedOrientation(decorativeObject.getConfig()), + orientation, ObjectType.DECORATIVE_OBJECT ); } @@ -372,7 +374,7 @@ private void upload(SceneContext sceneContext, @Nonnull Tile tile, int tileExX, Renderable renderable2 = decorativeObject.getRenderable2(); if (renderable2 instanceof Model) { uploadModel(sceneContext, tile, ModelHash.packUuid(ModelHash.TYPE_OBJECT, decorativeObject.getId()), (Model) renderable2, - HDUtils.getBakedOrientation(decorativeObject.getConfig()), + orientation, ObjectType.DECORATIVE_OBJECT ); } @@ -496,10 +498,11 @@ private int[] uploadHDTilePaintSurface( if (waterType == WaterType.NONE) { if (textureId != -1) { - swMaterial = Material.fromVanillaTexture(textureId); - seMaterial = Material.fromVanillaTexture(textureId); - neMaterial = Material.fromVanillaTexture(textureId); - nwMaterial = Material.fromVanillaTexture(textureId); + var material = Material.fromVanillaTexture(textureId); + // Disable tile overrides for newly introduced vanilla textures + if (material == Material.VANILLA) + override = NONE; + swMaterial = seMaterial = neMaterial = nwMaterial = material; } swNormals = sceneContext.vertexTerrainNormals.getOrDefault(swVertexKey, swNormals); @@ -862,9 +865,11 @@ private int[] uploadHDTileModelSurface(SceneContext sceneContext, Tile tile, Sce waterType = proceduralGenerator.seasonalWaterType(override, textureId); if (waterType == WaterType.NONE) { if (textureId != -1) { - materialA = Material.fromVanillaTexture(textureId); - materialB = Material.fromVanillaTexture(textureId); - materialC = Material.fromVanillaTexture(textureId); + var material = Material.fromVanillaTexture(textureId); + // Disable tile overrides for newly introduced vanilla textures + if (material == Material.VANILLA) + override = NONE; + materialA = materialB = materialC = material; } normalsA = sceneContext.vertexTerrainNormals.getOrDefault(vertexKeyA, normalsA); diff --git a/src/main/java/rs117/hd/scene/TextureManager.java b/src/main/java/rs117/hd/scene/TextureManager.java index 1b13377f66..92d3b9f284 100644 --- a/src/main/java/rs117/hd/scene/TextureManager.java +++ b/src/main/java/rs117/hd/scene/TextureManager.java @@ -146,7 +146,7 @@ private static class TextureLayer { private int[] vanillaTextureIndexToMaterialUniformIndex = {}; public int getMaterialIndex(@Nonnull Material material, int vanillaTextureIndex) { - if (material == Material.NONE && + if (material == Material.VANILLA && vanillaTextureIndex >= 0 && vanillaTextureIndex < vanillaTextureIndexToMaterialUniformIndex.length) return vanillaTextureIndexToMaterialUniformIndex[vanillaTextureIndex]; @@ -466,7 +466,8 @@ private void writeMaterialData(ByteBuffer buffer, MaterialEntry entry) { .putFloat(scrollSpeedY) .putFloat(1 / m.textureScale[0]) .putFloat(1 / m.textureScale[1]) - .putFloat(0).putFloat(0); // align vec4 + .putFloat(1 / m.textureScale[2]) + .putFloat(0); // align vec4 } private ByteBuffer generateWaterTypeUniformBuffer() { diff --git a/src/main/java/rs117/hd/scene/TileOverrideManager.java b/src/main/java/rs117/hd/scene/TileOverrideManager.java index ce7b38542e..3bed7198ae 100644 --- a/src/main/java/rs117/hd/scene/TileOverrideManager.java +++ b/src/main/java/rs117/hd/scene/TileOverrideManager.java @@ -249,23 +249,21 @@ public TileOverride getOverrideBeforeReplacements(@Nonnull int[] worldPos, int.. outer: for (int id : ids) { var entries = idMatchOverrides.get(id); - if (entries != null) { - for (var entry : entries) { - var area = entry.getKey(); - if (area.containsPoint(worldPos)) { - match = entry.getValue(); - match.queriedAsOverlay = (id & OVERLAY_FLAG) != 0; - break outer; - } + for (var entry : entries) { + var area = entry.getKey(); + if (area.containsPoint(worldPos)) { + match = entry.getValue(); + match.queriedAsOverlay = (id & OVERLAY_FLAG) != 0; + break outer; } } } for (var entry : anyMatchOverrides) { - var area = entry.getKey(); var override = entry.getValue(); if (override.index > match.index) break; + var area = entry.getKey(); if (area.containsPoint(worldPos)) { match = override; break; diff --git a/src/main/java/rs117/hd/scene/environments/Environment.java b/src/main/java/rs117/hd/scene/environments/Environment.java index 379aff4720..3560238897 100644 --- a/src/main/java/rs117/hd/scene/environments/Environment.java +++ b/src/main/java/rs117/hd/scene/environments/Environment.java @@ -36,6 +36,7 @@ public class Environment { public boolean isUnderwater = false; public boolean allowSkyOverride = true; public boolean lightningEffects = false; + public boolean instantTransition = false; @JsonAdapter(SrgbToLinearAdapter.class) public float[] ambientColor = rgb("#ffffff"); public float ambientStrength = 1; diff --git a/src/main/java/rs117/hd/scene/lights/Alignment.java b/src/main/java/rs117/hd/scene/lights/Alignment.java index efed58f6b9..fc632112a4 100644 --- a/src/main/java/rs117/hd/scene/lights/Alignment.java +++ b/src/main/java/rs117/hd/scene/lights/Alignment.java @@ -2,6 +2,8 @@ public enum Alignment { + CUSTOM(0, false, true), + @Deprecated CENTER(0, false, false), NORTH(0, true, false), diff --git a/src/main/java/rs117/hd/scene/lights/Light.java b/src/main/java/rs117/hd/scene/lights/Light.java index 4b0b0db3ab..36d34cae0c 100644 --- a/src/main/java/rs117/hd/scene/lights/Light.java +++ b/src/main/java/rs117/hd/scene/lights/Light.java @@ -6,6 +6,8 @@ public class Light { + public static final float VISIBILITY_FADE = 0.1f; + public final float randomOffset = HDUtils.rand.nextFloat(); public final LightDefinition def; @@ -22,45 +24,106 @@ public class Light public float spawnDelay; public float despawnDelay; - public float elapsedTime; - public boolean visible = true; - public boolean isImpostor; + public boolean visible; + public boolean parentExists; + public boolean withinViewingDistance = true; + public boolean hiddenTemporarily; public boolean markedForRemoval; - public float scheduledDespawnTime = -1; + public boolean persistent; + public boolean replayable; + + public final boolean animationSpecific; + public final boolean dynamicLifetime; + + public float elapsedTime; + public float changedVisibilityAt = -1; + public float lifetime = -1; public WorldPoint worldPoint; - public int x; - public int y; - public int z; - public int plane; - public int distanceSquared = 0; public boolean belowFloor; public boolean aboveFloor; + public int plane; + public int prevPlane = -1; + public int prevTileX = -1; + public int prevTileY = -1; + public Alignment alignment; + public int[] origin = new int[3]; + public int[] offset = new int[3]; + public int[] pos = new int[3]; + public int orientation; + public int distanceSquared; public Actor actor; public Projectile projectile; - public TileObject object; + public TileObject tileObject; public GraphicsObject graphicsObject; - public int objectId = -1; public int spotAnimId = -1; + public int preOrientation; + public int[] projectileRefCounter; + public long hash; + + public int sizeX = 1; + public int sizeY = 1; public Light(LightDefinition def) { this.def = def; - duration = def.duration / 1000f; - fadeInDuration = def.fadeInDuration / 1000f; - fadeOutDuration = def.fadeOutDuration / 1000f; - spawnDelay = def.spawnDelay / 1000f; - despawnDelay = def.despawnDelay / 1000f; + System.arraycopy(def.offset, 0, offset, 0, 3); + duration = Math.max(0, def.duration) / 1000f; + fadeInDuration = Math.max(0, def.fadeInDuration) / 1000f; + fadeOutDuration = Math.max(0, def.fadeOutDuration) / 1000f; + spawnDelay = Math.max(0, def.spawnDelay) / 1000f; + despawnDelay = Math.max(0, def.despawnDelay) / 1000f; color = def.color; radius = def.radius; strength = def.strength; + alignment = def.alignment; plane = def.plane; if (def.type == LightType.PULSE) animation = (float) Math.random(); - if (def.fixedDespawnTime) { - markedForRemoval = true; - scheduledDespawnTime = spawnDelay + despawnDelay; + // Old way of setting a fixed lifetime + if (def.fixedDespawnTime) + lifetime = spawnDelay + despawnDelay; + + if (lifetime == -1) { + dynamicLifetime = true; + // If the despawn is dynamic, ensure there's enough time for the light to fade out + despawnDelay = Math.max(despawnDelay, fadeOutDuration); + } else { + dynamicLifetime = false; } + + animationSpecific = !def.animationIds.isEmpty(); + if (animationSpecific) { + persistent = replayable = true; + // Initially hide the light + if (dynamicLifetime) { + lifetime = 0; + } else { + elapsedTime = lifetime; + } + } + } + + public void toggleTemporaryVisibility() { + hiddenTemporarily = !hiddenTemporarily; + // Begin fading in or out, while accounting for time already spent fading out or in respectively + float beginFadeAt = elapsedTime; + if (changedVisibilityAt != -1) + beginFadeAt -= Math.max(0, VISIBILITY_FADE - (elapsedTime - changedVisibilityAt)); + changedVisibilityAt = beginFadeAt; + } + + public float getTemporaryVisibilityFade() { + float fade = 1; + if (changedVisibilityAt != -1) + fade = HDUtils.clamp((elapsedTime - changedVisibilityAt) / Light.VISIBILITY_FADE, 0, 1); + if (hiddenTemporarily) + fade = 1 - fade; // Fade out instead + return fade; + } + + public void applyTemporaryVisibilityFade() { + strength *= getTemporaryVisibilityFade(); } } diff --git a/src/main/java/rs117/hd/scene/lights/LightDefinition.java b/src/main/java/rs117/hd/scene/lights/LightDefinition.java index b59bc32b2d..db2374f190 100644 --- a/src/main/java/rs117/hd/scene/lights/LightDefinition.java +++ b/src/main/java/rs117/hd/scene/lights/LightDefinition.java @@ -1,6 +1,7 @@ package rs117.hd.scene.lights; import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; import java.util.HashSet; import javax.annotation.Nullable; import lombok.NoArgsConstructor; @@ -13,7 +14,8 @@ public class LightDefinition { @Nullable public Integer worldX, worldY; public int plane; - public Alignment alignment = Alignment.CENTER; + public Alignment alignment = Alignment.CUSTOM; + public int[] offset = new int[3]; public int height; public int radius = 300; public float strength = 5; @@ -24,11 +26,11 @@ public class LightDefinition { public float range; public int fadeInDuration = 50; public int fadeOutDuration = 50; - public boolean fadeOverlap; public int spawnDelay; public int despawnDelay; public boolean fixedDespawnTime; - public boolean visibleFromOtherPlanes = false; + public boolean visibleFromOtherPlanes; + public int renderableIndex = -1; @JsonAdapter(GsonUtils.IntegerSetAdapter.class) public HashSet npcIds = new HashSet<>(); @JsonAdapter(GsonUtils.IntegerSetAdapter.class) @@ -36,16 +38,22 @@ public class LightDefinition { @JsonAdapter(GsonUtils.IntegerSetAdapter.class) public HashSet projectileIds = new HashSet<>(); @JsonAdapter(GsonUtils.IntegerSetAdapter.class) - public HashSet graphicsObjectIds = new HashSet<>(); + @SerializedName("graphicsObjectIds") // TODO: rename this + public HashSet spotAnimIds = new HashSet<>(); @JsonAdapter(GsonUtils.IntegerSetAdapter.class) public HashSet animationIds = new HashSet<>(); public void normalize() { if (description == null) description = "N/A"; - if (alignment == null) - alignment = Alignment.CENTER; - if (color == null) + if (alignment == null || alignment == Alignment.CENTER) + alignment = Alignment.CUSTOM; + if (offset == null || offset.length != 3) { + offset = new int[3]; + } else { + offset[1] *= -1; + } + if (color == null || color.length != 3) color = new float[3]; if (type == null) type = LightType.STATIC; diff --git a/src/main/java/rs117/hd/scene/lights/TileObjectImpostorTracker.java b/src/main/java/rs117/hd/scene/lights/TileObjectImpostorTracker.java new file mode 100644 index 0000000000..83d2ac9f37 --- /dev/null +++ b/src/main/java/rs117/hd/scene/lights/TileObjectImpostorTracker.java @@ -0,0 +1,39 @@ +package rs117.hd.scene.lights; + +import javax.annotation.Nullable; +import net.runelite.api.*; +import net.runelite.api.coords.*; + +public class TileObjectImpostorTracker { + public TileObject tileObject; + public final long tileObjectHash; + public boolean justSpawned = true; + public boolean spawnedAnyLights; + public int[] impostorIds; + public int impostorVarbit = -1; + public int impostorVarp = -1; + public int impostorId = -1; + + public TileObjectImpostorTracker(TileObject tileObject) { + this.tileObject = tileObject; + this.tileObjectHash = tileObjectHash(tileObject); + } + + public long lightHash(int impostorId) { + long hash = this.tileObjectHash; + hash = hash * 31 + impostorId; + return hash; + } + + private static long tileObjectHash(@Nullable TileObject tileObject) { + if (tileObject == null) + return 0; + + LocalPoint lp = tileObject.getLocalLocation(); + long hash = lp.getX(); + hash = hash * 31 + lp.getY(); + hash = hash * 31 + tileObject.getPlane(); + hash = hash * 31 + tileObject.getId(); + return hash; + } +} 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 bcc31a6c5e..14cb95d15b 100644 --- a/src/main/java/rs117/hd/scene/model_overrides/ModelOverride.java +++ b/src/main/java/rs117/hd/scene/model_overrides/ModelOverride.java @@ -15,8 +15,8 @@ import rs117.hd.data.materials.UvType; import rs117.hd.utils.AABB; import rs117.hd.utils.GsonUtils; -import rs117.hd.utils.HDUtils; import rs117.hd.utils.Props; +import rs117.hd.utils.Vector; import static net.runelite.api.Perspective.*; @@ -327,11 +327,12 @@ private void computeBoxUvw(float[] out, Model model, int modelOrientation, int f // Compute face normal float[] a = new float[3]; float[] b = new float[3]; - HDUtils.subtract(a, v[1], v[0]); - HDUtils.subtract(b, v[2], v[0]); + Vector.subtract(a, v[1], v[0]); + Vector.subtract(b, v[2], v[0]); float[] n = new float[3]; - HDUtils.cross(n, a, b); - float[] absN = HDUtils.abs(a, n); + Vector.cross(n, a, b); + float[] absN = new float[3]; + Vector.abs(absN, n); out[2] = out[6] = out[10] = 0; if (absN[0] > absN[1] && absN[0] > absN[2]) { diff --git a/src/main/java/rs117/hd/scene/tile_overrides/TileOverride.java b/src/main/java/rs117/hd/scene/tile_overrides/TileOverride.java index c8445fdbd2..9a27bc64e5 100644 --- a/src/main/java/rs117/hd/scene/tile_overrides/TileOverride.java +++ b/src/main/java/rs117/hd/scene/tile_overrides/TileOverride.java @@ -57,6 +57,7 @@ public class TileOverride { private TileOverride(@Nullable String name, GroundMaterial groundMaterial) { this.name = name; this.groundMaterial = groundMaterial; + this.index = Integer.MAX_VALUE; // Prioritize any-match overrides over this } @Override @@ -65,21 +66,26 @@ public String toString() { return name; if (description != null) return description; + if (area != null) + return area.name(); return "Unnamed"; } public void normalize(TileOverride[] allOverrides, Map constants) { int numOverlays = overlayIds == null ? 0 : overlayIds.length; int numUnderlays = underlayIds == null ? 0 : underlayIds.length; - ids = new int[numOverlays + numUnderlays]; - int i = 0; - for (int j = 0; j < numOverlays; j++) { - int id = overlayIds[j]; - ids[i++] = OVERLAY_FLAG | id; - } - for (int j = 0; j < numUnderlays; j++) { - int id = underlayIds[j]; - ids[i++] = id; + int numIds = numOverlays + numUnderlays; + if (numIds > 0) { + ids = new int[numOverlays + numUnderlays]; + int i = 0; + for (int j = 0; j < numOverlays; j++) { + int id = overlayIds[j]; + ids[i++] = OVERLAY_FLAG | id; + } + for (int j = 0; j < numUnderlays; j++) { + int id = underlayIds[j]; + ids[i++] = id; + } } if (area == null) { diff --git a/src/main/java/rs117/hd/utils/ColorUtils.java b/src/main/java/rs117/hd/utils/ColorUtils.java index c53a97e5a2..7bed56ac2c 100644 --- a/src/main/java/rs117/hd/utils/ColorUtils.java +++ b/src/main/java/rs117/hd/utils/ColorUtils.java @@ -112,14 +112,14 @@ public static float srgbToLinear(float c) { (float) Math.pow((c + 0.055) / 1.055, 2.4); } - public static float[] linearToSrgb(float[] c) { + public static float[] linearToSrgb(float... c) { float[] result = new float[c.length]; for (int i = 0; i < c.length; i++) result[i] = linearToSrgb(c[i]); return result; } - public static float[] srgbToLinear(float[] c) { + public static float[] srgbToLinear(float... c) { float[] result = new float[c.length]; for (int i = 0; i < c.length; i++) result[i] = srgbToLinear(c[i]); @@ -142,7 +142,7 @@ private static int clamp(int value, int min, int max) { } /** - * Convert sRGB in the range 0-1 from sRGB to HSL in the range 0-1. + * Convert sRGB in the range 0-1 to HSL in the range 0-1. * * @param srgb float[3] * @return hsl float[3] @@ -189,11 +189,23 @@ public static float[] hslToSrgb(float[] hsl) { return new float[] { r, g, b }; } + /** + * Convert HSL in the range 0-1 to HSV in the range 0-1. + * + * @param hsl float[3] + * @return hsv float[3] + */ public static float[] hslToHsv(float[] hsl) { float v = hsl[2] + hsl[1] * Math.min(hsl[2], 1 - hsl[2]); return new float[] { hsl[0], Math.abs(v) < EPS ? 0 : 2 * (1 - hsl[2] / v), v }; } + /** + * Convert HSV in the range 0-1 to HSL in the range 0-1. + * + * @param hsv float[3] + * @return hsl float[3] + */ public static float[] hsvToHsl(float[] hsv) { float l = hsv[2] * (1 - hsv[1] / 2); float divisor = Math.min(l, 1 - l); @@ -222,6 +234,8 @@ public static float[] hsvToSrgb(float[] hsv) { return hslToSrgb(hsvToHsl(hsv)); } + // Convenience functions for converting different formats into linear RGB, sRGB or packed HSL + /** * Convert red, green and blue in the range 0-255 from sRGB to linear RGB in the range 0-1. * @@ -247,11 +261,11 @@ public static float[] rgb(String hex) { /** * Convert sRGB color packed as an int to linear RGB in the range 0-1. * - * @param rgbInt RGB hex color + * @param srgb packed sRGB * @return float[3] linear rgb values from 0-1 */ - public static float[] rgb(int rgbInt) { - return srgbToLinear(srgb(rgbInt)); + public static float[] rgb(int srgb) { + return srgbToLinear(srgb(srgb)); } /** @@ -260,7 +274,7 @@ public static float[] rgb(int rgbInt) { * @param r red color * @param g green color * @param b blue color - * @return float[3] non-linear srgb values from 0-1 + * @return float[3] non-linear sRGB values from 0-1 */ public static float[] srgb(float r, float g, float b) { return new float[] { r / 255f, g / 255f, b / 255f }; @@ -270,7 +284,7 @@ public static float[] srgb(float r, float g, float b) { * Convert hex color from sRGB to sRGB in the range 0-1. * * @param hex RGB hex color - * @return float[3] non-linear srgb values from 0-1 + * @return float[3] non-linear sRGB values from 0-1 */ public static float[] srgb(String hex) { Color color = Color.decode(hex); @@ -280,74 +294,133 @@ public static float[] srgb(String hex) { /** * Convert sRGB color packed as an int to sRGB in the range 0-1. * - * @param rgbInt RGB hex color - * @return float[3] non-linear srgb values from 0-1 + * @param srgb packed sRGB + * @return float[3] non-linear sRGB values from 0-1 */ - public static float[] srgb(int rgbInt) { + public static float[] srgb(int srgb) { return new float[] { - (rgbInt >> 16 & 0xFF) / (float) 0xFF, - (rgbInt >> 8 & 0xFF) / (float) 0xFF, - (rgbInt & 0xFF) / (float) 0xFF, + (srgb >> 16 & 0xFF) / (float) 0xFF, + (srgb >> 8 & 0xFF) / (float) 0xFF, + (srgb & 0xFF) / (float) 0xFF, }; } - public static float[] srgba(int argbInt) { + /** + * Convert alpha and sRGB color packed in an int as ARGB to sRGB in the range 0-1. + * + * @param alphaSrgb packed sRGB with a preceding alpha channel + * @return float[4] non-linear sRGB and alpha in the range 0-1 + */ + public static float[] srgba(int alphaSrgb) { return new float[] { - (argbInt >> 16 & 0xFF) / (float) 0xFF, - (argbInt >> 8 & 0xFF) / (float) 0xFF, - (argbInt & 0xFF) / (float) 0xFF, - (argbInt >> 24 & 0xFF) / (float) 0xFF + (alphaSrgb >> 16 & 0xFF) / (float) 0xFF, + (alphaSrgb >> 8 & 0xFF) / (float) 0xFF, + (alphaSrgb & 0xFF) / (float) 0xFF, + (alphaSrgb >> 24 & 0xFF) / (float) 0xFF }; } - public static int packHsl(float[] hsl) { - int H = clamp(Math.round((hsl[0] - .0078125f) * (0x3F + 1)), 0, 0x3F); - int S = clamp(Math.round((hsl[1] - .0625f) * (0x7 + 1)), 0, 0x7); - int L = clamp(Math.round(hsl[2] * (0x7F + 1)), 0, 0x7F); - return H << 10 | S << 7 | L; + /** + * Convert red, green and blue in the range 0-255 from sRGB to packed HSL. + * + * @param r red color + * @param g green color + * @param b blue color + * @return int packed HSL + */ + public static int hsl(float r, float g, float b) { + return srgbToPackedHsl(srgb(r, g, b)); } - public static float[] unpackHsl(int hsl) { + /** + * Convert hex color from sRGB to packed HSL. + * + * @param rgbHex RGB hex color + * @return int packed HSL + */ + public static int hsl(String rgbHex) { + return srgbToPackedHsl(srgb(rgbHex)); + } + + /** + * Convert sRGB color packed as an int to packed HSL. + * + * @param packedSrgb RGB hex color + * @return int packed HSL + */ + public static int hsl(int packedSrgb) { + return srgbToPackedHsl(srgb(packedSrgb)); + } + + // Integer packing and unpacking functions + + public static int packRawRgb(int... rgb) { + return rgb[0] << 16 | rgb[1] << 8 | rgb[2]; + } + + public static int packSrgb(float[] srgb) { + int[] ints = new int[3]; + for (int i = 0; i < 3; i++) + ints[i] = clamp(Math.round(srgb[i] * 0xFF), 0, 0xFF); + return packRawRgb(ints); + } + + public static int packRawHsl(int... hsl) { + return hsl[0] << 10 | hsl[1] << 7 | hsl[2]; + } + + public static void unpackRawHsl(int[] out, int hsl) { // 6-bit hue | 3-bit saturation | 7-bit lightness - float H = (hsl >> 10 & 0x3F) / (0x3F + 1f) + .0078125f; - float S = (hsl >> 7 & 0x7) / (0x7 + 1f) + .0625f; - float L = (hsl & 0x7F) / (0x7F + 1f); - return new float[] { H, S, L }; + out[0] = hsl >>> 10 & 0x3F; + out[1] = hsl >>> 7 & 0x7; + out[2] = hsl & 0x7F; } - public static int[] unpackHslRaw(int hsl) { - var out = new int[3]; - unpackHslRaw(out, hsl); + public static int[] unpackRawHsl(int hsl) { + int[] out = new int[3]; + unpackRawHsl(out, hsl); return out; } - public static void unpackHslRaw(int[] out, int hsl) { - // 6-bit hue | 3-bit saturation | 7-bit lightness - out[0] = clamp(hsl >> 10 & 0x3F, 0, 0x3F); - out[1] = clamp(hsl >> 7 & 0x7, 0, 0x7); - out[2] = clamp(hsl & 0x7F, 0, 0x7F); + public static int packHsl(float... hsl) { + int H = clamp(Math.round((hsl[0] - .0078125f) * (0x3F + 1)), 0, 0x3F); + int S = clamp(Math.round((hsl[1] - .0625f) * (0x7 + 1)), 0, 0x7); + int L = clamp(Math.round(hsl[2] * (0x7F + 1)), 0, 0x7F); + return packRawHsl(H, S, L); } - public static int packHslRaw(int... hsl) { - return hsl[0] << 10 | hsl[1] << 7 | hsl[2]; + public static float[] unpackHsl(int hsl) { + // 6-bit hue | 3-bit saturation | 7-bit lightness + float H = (hsl >>> 10 & 0x3F) / (0x3F + 1f) + .0078125f; + float S = (hsl >>> 7 & 0x7) / (0x7 + 1f) + .0625f; + float L = (hsl & 0x7F) / (0x7F + 1f); + return new float[] { H, S, L }; } public static int srgbToPackedHsl(float[] srgb) { return packHsl(srgbToHsl(srgb)); } - public static float[] packedHslToSrgb(int hsl) { - return hslToSrgb(unpackHsl(hsl)); + public static float[] packedHslToSrgb(int packedHsl) { + return hslToSrgb(unpackHsl(packedHsl)); } - public static int linearRgbToPackedHsl(float[] srgb) { - return srgbToPackedHsl(linearToSrgb(srgb)); + public static int linearRgbToPackedHsl(float[] linearRgb) { + return srgbToPackedHsl(linearToSrgb(linearRgb)); } public static float[] packedHslToLinearRgb(int hsl) { return srgbToLinear(packedHslToSrgb(hsl)); } + public static String srgbToHex(float... srgb) { + return String.format("#%h", packSrgb(srgb)); + } + + public static String rgbToHex(float... linearRgb) { + return srgbToHex(linearToSrgb(linearRgb)); + } + @Slf4j public static class SrgbAdapter extends TypeAdapter { private final float[] rgba = { 0, 0, 0, 1 }; diff --git a/src/main/java/rs117/hd/utils/DeveloperTools.java b/src/main/java/rs117/hd/utils/DeveloperTools.java index e6ea7f578a..6f2ed8b83b 100644 --- a/src/main/java/rs117/hd/utils/DeveloperTools.java +++ b/src/main/java/rs117/hd/utils/DeveloperTools.java @@ -10,8 +10,9 @@ import net.runelite.client.eventbus.Subscribe; import net.runelite.client.input.KeyListener; import net.runelite.client.input.KeyManager; +import rs117.hd.HdPlugin; import rs117.hd.data.environments.Area; -import rs117.hd.overlays.FrameTimingsOverlay; +import rs117.hd.overlays.FrameTimerOverlay; import rs117.hd.overlays.LightGizmoOverlay; import rs117.hd.overlays.ShadowMapOverlay; import rs117.hd.overlays.TileInfoOverlay; @@ -23,6 +24,7 @@ public class DeveloperTools implements KeyListener { private static final Keybind KEY_TOGGLE_FRAME_TIMINGS = new Keybind(KeyEvent.VK_F4, InputEvent.CTRL_DOWN_MASK); private static final Keybind KEY_TOGGLE_SHADOW_MAP_OVERLAY = new Keybind(KeyEvent.VK_F5, InputEvent.CTRL_DOWN_MASK); private static final Keybind KEY_TOGGLE_LIGHT_GIZMO_OVERLAY = new Keybind(KeyEvent.VK_F6, InputEvent.CTRL_DOWN_MASK); + private static final Keybind KEY_TOGGLE_FREEZE_FRAME = new Keybind(KeyEvent.VK_ESCAPE, InputEvent.SHIFT_DOWN_MASK); @Inject private EventBus eventBus; @@ -30,11 +32,14 @@ public class DeveloperTools implements KeyListener { @Inject private KeyManager keyManager; + @Inject + private HdPlugin plugin; + @Inject private TileInfoOverlay tileInfoOverlay; @Inject - private FrameTimingsOverlay frameTimingsOverlay; + private FrameTimerOverlay frameTimerOverlay; @Inject private ShadowMapOverlay shadowMapOverlay; @@ -42,22 +47,26 @@ public class DeveloperTools implements KeyListener { @Inject private LightGizmoOverlay lightGizmoOverlay; + private boolean keyBindingsEnabled = false; private boolean tileInfoOverlayEnabled = false; private boolean frameTimingsOverlayEnabled = false; private boolean shadowMapOverlayEnabled = false; private boolean lightGizmoOverlayEnabled = false; public void activate() { + // Listen for commands eventBus.register(this); // Don't do anything else unless we're in the development environment if (!Props.DEVELOPMENT) return; + // Enable 117 HD's keybindings by default during development + keyBindingsEnabled = true; keyManager.registerKeyListener(this); tileInfoOverlay.setActive(tileInfoOverlayEnabled); - frameTimingsOverlay.setActive(frameTimingsOverlayEnabled); + frameTimerOverlay.setActive(frameTimingsOverlayEnabled); shadowMapOverlay.setActive(shadowMapOverlayEnabled); lightGizmoOverlay.setActive(lightGizmoOverlayEnabled); @@ -79,7 +88,7 @@ public void deactivate() { eventBus.unregister(this); keyManager.unregisterKeyListener(this); tileInfoOverlay.setActive(false); - frameTimingsOverlay.setActive(false); + frameTimerOverlay.setActive(false); shadowMapOverlay.setActive(false); lightGizmoOverlay.setActive(false); } @@ -99,7 +108,7 @@ public void onCommandExecuted(CommandExecuted commandExecuted) { tileInfoOverlay.setActive(tileInfoOverlayEnabled = !tileInfoOverlayEnabled); break; case "timers": - frameTimingsOverlay.setActive(frameTimingsOverlayEnabled = !frameTimingsOverlayEnabled); + frameTimerOverlay.setActive(frameTimingsOverlayEnabled = !frameTimingsOverlayEnabled); break; case "shadowmap": shadowMapOverlay.setActive(shadowMapOverlayEnabled = !shadowMapOverlayEnabled); @@ -107,30 +116,33 @@ public void onCommandExecuted(CommandExecuted commandExecuted) { case "lights": lightGizmoOverlay.setActive(lightGizmoOverlayEnabled = !lightGizmoOverlayEnabled); break; + case "keybindings": + keyBindingsEnabled = !keyBindingsEnabled; + if (keyBindingsEnabled) { + keyManager.registerKeyListener(this); + } else { + keyManager.unregisterKeyListener(this); + } + break; } } @Override - public void keyPressed(KeyEvent event) { - if (KEY_TOGGLE_TILE_INFO.matches(event)) { - event.consume(); + public void keyPressed(KeyEvent e) { + if (KEY_TOGGLE_TILE_INFO.matches(e)) { tileInfoOverlay.setActive(tileInfoOverlayEnabled = !tileInfoOverlayEnabled); - } - - if (KEY_TOGGLE_FRAME_TIMINGS.matches(event)) { - event.consume(); - frameTimingsOverlay.setActive(frameTimingsOverlayEnabled = !frameTimingsOverlayEnabled); - } - - if (KEY_TOGGLE_SHADOW_MAP_OVERLAY.matches(event)) { - event.consume(); + } else if (KEY_TOGGLE_FRAME_TIMINGS.matches(e)) { + frameTimerOverlay.setActive(frameTimingsOverlayEnabled = !frameTimingsOverlayEnabled); + } else if (KEY_TOGGLE_SHADOW_MAP_OVERLAY.matches(e)) { shadowMapOverlay.setActive(shadowMapOverlayEnabled = !shadowMapOverlayEnabled); - } - - if (KEY_TOGGLE_LIGHT_GIZMO_OVERLAY.matches(event)) { - event.consume(); + } else if (KEY_TOGGLE_LIGHT_GIZMO_OVERLAY.matches(e)) { lightGizmoOverlay.setActive(lightGizmoOverlayEnabled = !lightGizmoOverlayEnabled); + } else if (KEY_TOGGLE_FREEZE_FRAME.matches(e)) { + plugin.toggleFreezeFrame(); + } else { + return; } + e.consume(); } @Override diff --git a/src/main/java/rs117/hd/utils/ExpressionParser.java b/src/main/java/rs117/hd/utils/ExpressionParser.java index 997da0c006..3968dfeeef 100644 --- a/src/main/java/rs117/hd/utils/ExpressionParser.java +++ b/src/main/java/rs117/hd/utils/ExpressionParser.java @@ -62,7 +62,6 @@ public static class SyntaxError extends IllegalArgumentException { @RequiredArgsConstructor private enum Operator { - NOT("!", 7, 1), MOD("%", 6, 2), MUL("*", 6, 2), DIV("/", 6, 2), @@ -73,6 +72,7 @@ private enum Operator { GEQUAL(">=", 4, 2), GREATER(">", 4, 2), NOTEQUAL("!=", 3, 2), + NOT("!", 7, 1), EQUAL("==", 3, 2), AND("&&", 2, 2), OR("||", 1, 2), diff --git a/src/main/java/rs117/hd/utils/HDUtils.java b/src/main/java/rs117/hd/utils/HDUtils.java index 96a03f0e6c..2b02c82fc4 100644 --- a/src/main/java/rs117/hd/utils/HDUtils.java +++ b/src/main/java/rs117/hd/utils/HDUtils.java @@ -60,53 +60,6 @@ public class HDUtils { // directional vectors approximately opposite of the directional light used by the client private static final float[] LIGHT_DIR_TILE = new float[] { 0.70710678f, 0.70710678f, 0f }; - /** - * Computes a + b, storing it in the out array - */ - public static float[] add(float[] out, float[] a, float[] b) { - for (int i = 0; i < out.length; i++) - out[i] = a[i] + b[i]; - return out; - } - - /** - * Computes a - b, storing it in the out array - */ - public static float[] subtract(float[] out, float[] a, float[] b) { - for (int i = 0; i < out.length; i++) - out[i] = a[i] - b[i]; - return out; - } - - public static float[] cross(float[] out, float[] a, float[] b) { - out[0] = a[1] * b[2] - a[2] * b[1]; - out[1] = a[2] * b[0] - a[0] * b[2]; - out[2] = a[0] * b[1] - a[1] * b[0]; - return out; - } - - public static float length(float... vector) { - float lengthSquared = 0; - for (float v : vector) - lengthSquared += v * v; - return (float) Math.sqrt(lengthSquared); - } - - public static void normalize(float[] vector) { - float length = length(vector); - if (length == 0) - return; - length = 1 / length; - for (int i = 0; i < vector.length; i++) - vector[i] *= length; - } - - public static float[] abs(float[] out, float[] v) { - for (int i = 0; i < out.length; i++) - out[i] = Math.abs(v[i]); - return out; - } - public static float min(float... v) { float min = v[0]; for (int i = 1; i < v.length; i++) @@ -182,7 +135,7 @@ public static float mod(float x, float modulus) { * Modulo that returns the answer with the same sign as the modulus. */ public static int mod(int x, int modulus) { - return x - (x / modulus) * modulus; + return ((x % modulus) + modulus) % modulus; } public static float clamp(float value, float min, float max) { @@ -202,10 +155,10 @@ public static int vertexHash(int[] vPos) { } public static float[] calculateSurfaceNormals(float[] a, float[] b, float[] c) { - subtract(b, a, b); - subtract(c, a, c); + Vector.subtract(b, a, b); + Vector.subtract(c, a, c); float[] n = new float[3]; - return cross(n, b, c); + return Vector.cross(n, b, c); } public static float dotLightDirectionTile(float x, float y, float z) { @@ -350,11 +303,15 @@ public static WorldPoint getSceneBase(Scene scene, int plane) return new WorldPoint(baseX, baseY, plane); } - public static int[] cameraSpaceToWorldPoint(Client client, int relativeX, int relativeZ) { + public static int[] cameraSpaceToLocalPoint(Client client, int relativeX, int relativeZ) { int localX = client.getCameraX2() + relativeX; int localY = client.getCameraZ2() + relativeZ; - int plane = client.getPlane(); - return localToWorld(client.getScene(), localX, localY, plane); + return new int[] { localX, localY }; + } + + public static int[] cameraSpaceToWorldPoint(Client client, int relativeX, int relativeZ) { + int[] local = cameraSpaceToLocalPoint(client, relativeX, relativeZ); + return localToWorld(client.getScene(), local[0], local[1], client.getPlane()); } /** @@ -463,7 +420,7 @@ public static void getSouthWesternMostTileColor(int[] out, Tile tile) { var paint = tile.getSceneTilePaint(); var model = tile.getSceneTileModel(); if (paint != null) { - ColorUtils.unpackHslRaw(out, paint.getSwColor()); + ColorUtils.unpackRawHsl(out, paint.getSwColor()); } else if (model != null) { int faceCount = tile.getSceneTileModel().getFaceX().length; final int[] faceColorsA = model.getTriangleColorA(); @@ -486,7 +443,7 @@ public static void getSouthWesternMostTileColor(int[] out, Tile tile) { } } - ColorUtils.unpackHslRaw(out, hsl); + ColorUtils.unpackRawHsl(out, hsl); } } } diff --git a/src/main/java/rs117/hd/utils/Mat4.java b/src/main/java/rs117/hd/utils/Mat4.java index 25c3ab50ad..94d1aa4b4e 100644 --- a/src/main/java/rs117/hd/utils/Mat4.java +++ b/src/main/java/rs117/hd/utils/Mat4.java @@ -28,7 +28,7 @@ public class Mat4 { /** - * Utility class to work with column-major 4x4 matrices. + * Utility class for working with column-major 4 x 4 matrices. */ public static float[] identity() @@ -270,10 +270,51 @@ public static void mulVec(float[] out, float[] mat4, float[] vec4) { public static void projectVec(float[] out, float[] mat4, float[] vec4) { mulVec(out, mat4, vec4); if (out[3] != 0) { - float reciprocal = 1 / out[3]; - for (int i = 0; i < 3; i++) + // The 4th component should retain information about whether the + // point lies behind the camera + float reciprocal = 1 / Math.abs(out[3]); + for (int i = 0; i < 4; i++) out[i] *= reciprocal; -// out[3] = 1; } } + + public static void transpose(float[] m) { + for (int i = 0; i < 4; i++) { + for (int j = i + 1; j < 4; j++) { + int a = i * 4 + j; + int b = j * 4 + i; + float temp = m[a]; + m[a] = m[b]; + m[b] = temp; + } + } + } + + public static float[] inverse(float[] m) { + float[] augmented = new float[32]; + System.arraycopy(m, 0, augmented, 0, 16); + for (int i = 0; i < 4; i++) + augmented[16 + i * 5] = 1; + + Matrix.solve(augmented, 4, 8); + + float[] inverse = new float[16]; + System.arraycopy(augmented, 16, inverse, 0, 16); + return inverse; + } + + public static void extractRow(float[] out, float[] mat4, int rowIndex) { + System.arraycopy(mat4, 4 * rowIndex, out, 0, out.length); + } + + public static void extractColumn(float[] out, float[] mat4, int columnIndex) { + for (int i = 0; i < out.length; i++) + out[i] = mat4[4 * i + columnIndex]; + } + + public static String format(float[] m) { + assert m.length == 16; + return Matrix.format(m, 4, 4); + } + } diff --git a/src/main/java/rs117/hd/utils/Matrix.java b/src/main/java/rs117/hd/utils/Matrix.java new file mode 100644 index 0000000000..6788df6806 --- /dev/null +++ b/src/main/java/rs117/hd/utils/Matrix.java @@ -0,0 +1,107 @@ +package rs117.hd.utils; + +import java.text.DecimalFormat; +import java.util.Arrays; + +public class Matrix { + /** + * Utility class for working with column-major m x n matrices. + */ + + public static float[] copy(float[] m) { + return Arrays.copyOf(m, m.length); + } + + public static void solve(float[] m, int rows, int columns) { + int square = Math.min(columns, rows); + columns: + for (int j = 0; j < square; j++) { + for (int i = j; i < rows; i++) { + var f = m[j * rows + i]; + if (f == 0) + continue; + + // Swap the row into the right position + if (i != j) { + for (int k = 0; k < rows * columns; k += rows) { + var tmp = m[k + j]; + m[k + j] = m[k + i]; + m[k + i] = tmp; + } + } + + // Divide by the first entry of the row + f = 1 / f; + for (int k = 0; k < rows * columns; k += rows) + m[k + j] *= f; + + // Add or subtract multiples to reduce other rows + for (int r = 0; r < rows; r++) { + if (r == j) + continue; + var g = m[j * rows + r]; + if (g != 0) + for (int k = 0; k < rows * columns; k += rows) + m[k + r] += -g * m[k + j]; + } + + continue columns; + } + + throw new IllegalArgumentException("Linear system does not have a solution"); + } + } + + public static String format(float[] m, int rows, int columns) { + String[] f = new String[m.length]; + var format = new DecimalFormat("0.##"); + int maxdigits = 0; + int maxfractions = 0; + for (int i = 0; i < rows * columns; i++) { + float v = m[i]; + if (Math.abs(v) < .01) + v = 0; + f[i] = format.format(v); + var j = f[i].indexOf('.'); + if (j == -1) { + maxdigits = Math.max(maxdigits, f[i].length()); + } else { + maxdigits = Math.max(maxdigits, j); + maxfractions = Math.max(maxfractions, f[i].length() - j); + } + } + + StringBuilder str = new StringBuilder(); + + for (int row = 0; row < rows; row++) { + for (int col = 0; col < columns; col++) { + int idx = col * rows + row; + int dot = f[idx].indexOf('.'); + if (dot == -1) { + int padLeft = maxdigits - f[idx].length(); + if (padLeft > 0) + f[idx] = " ".repeat(padLeft) + f[idx]; + f[idx] += " ".repeat(maxfractions); + } else { + int padLeft = maxdigits - dot; + if (padLeft > 0) + f[idx] = " ".repeat(padLeft) + f[idx]; + int padRight = maxfractions - (f[idx].length() - dot); + if (padRight > 0) + f[idx] += " ".repeat(padRight); + } + + if (col == 0) + str.append("[ "); + str.append(f[idx]).append(" "); + if (col == columns - 1) { + str.append("]"); + if (idx != m.length - 1) + str.append("\n"); + } + } + } + + return str.toString(); + } +} diff --git a/src/main/java/rs117/hd/utils/ResourcePath.java b/src/main/java/rs117/hd/utils/ResourcePath.java index f95ab18386..1afe12a742 100644 --- a/src/main/java/rs117/hd/utils/ResourcePath.java +++ b/src/main/java/rs117/hd/utils/ResourcePath.java @@ -185,7 +185,7 @@ public String toString() { String path = toPosixPath(); if (root != null) path = normalize(root.toPosixPath(), path.startsWith("/") ? path.substring(1) : path); - return path.length() == 0 ? "." : path; + return path.isEmpty() ? "." : path; } public ResourcePath toAbsolute() { @@ -283,18 +283,21 @@ public boolean isFileSystemResource() { * @return A runnable that can be called to unregister the watch callback */ public FileWatcher.UnregisterCallback watch(BiConsumer changeHandler) { - // Only watch files on the file system - if (RESOURCE_PATH == null) { - changeHandler.accept(this, true); - return NOOP; - } + var path = this; - // Attempt to redirect to the resource directory - ResourcePath path = RESOURCE_PATH.chroot().resolve(toAbsolute().toPath().toString()); + // Redirect to the project folder during development + if (RESOURCE_PATH != null) + path = RESOURCE_PATH.chroot().resolve(toAbsolute().toPath().toString()); // Load once up front changeHandler.accept(path, true); - return FileWatcher.watchPath(path, p -> changeHandler.accept(p, false)); + + // Watch for changes if the resource is on the file system, which will exclude paths pointing into the JAR + // By default, unless paths are overridden by VM arguments, all of 117 HD's paths point into the JAR + if (isFileSystemResource()) + return FileWatcher.watchPath(path, p -> changeHandler.accept(p, false)); + + return NOOP; } public FileWatcher.UnregisterCallback watch(Consumer changeHandler) { @@ -433,14 +436,14 @@ private static String normalize(String... parts) { private static String normalize(@Nullable String workingDirectory, String[] parts) { Stack resolvedParts = new Stack<>(); - if (workingDirectory != null && workingDirectory.length() > 0 && !workingDirectory.equals(".")) + if (workingDirectory != null && !workingDirectory.isEmpty() && !workingDirectory.equals(".")) resolvedParts.addAll(Arrays.asList(normalizeSlashes(workingDirectory).split("/"))); if (parts.length > 0) parts[0] = resolveTilde(parts[0]); for (String part : parts) { - if (part == null || part.length() == 0 || part.equals(".")) + if (part == null || part.isEmpty() || part.equals(".")) continue; part = normalizeSlashes(part); @@ -450,7 +453,7 @@ private static String normalize(@Nullable String workingDirectory, String[] part for (String normalizedPart : part.split("/")) { if (normalizedPart.equals("..") && - resolvedParts.size() > 0 && + !resolvedParts.isEmpty() && !resolvedParts.peek().equals("..") ) { resolvedParts.pop(); diff --git a/src/main/java/rs117/hd/utils/Vector.java b/src/main/java/rs117/hd/utils/Vector.java new file mode 100644 index 0000000000..76c05beb6c --- /dev/null +++ b/src/main/java/rs117/hd/utils/Vector.java @@ -0,0 +1,63 @@ +package rs117.hd.utils; + +import java.util.Arrays; + +public class Vector { + public static float[] copy(float[] v) { + return Arrays.copyOf(v, v.length); + } + + /** + * Computes a + b, storing it in the out array + */ + public static float[] add(float[] out, float[] a, float[] b) { + for (int i = 0; i < out.length; i++) + out[i] = a[i] + b[i]; + return out; + } + + /** + * Computes a - b, storing it in the out array + */ + public static float[] subtract(float[] out, float[] a, float[] b) { + for (int i = 0; i < out.length; i++) + out[i] = a[i] - b[i]; + return out; + } + + public static float dot(float[] a, float[] b) { + float f = 0; + int len = Math.min(a.length, b.length); + for (int i = 0; i < len; i++) + f += a[i] * b[i]; + return f; + } + + public static float[] cross(float[] out, float[] a, float[] b) { + out[0] = a[1] * b[2] - a[2] * b[1]; + out[1] = a[2] * b[0] - a[0] * b[2]; + out[2] = a[0] * b[1] - a[1] * b[0]; + return out; + } + + public static float length(float... vector) { + float lengthSquared = 0; + for (float v : vector) + lengthSquared += v * v; + return (float) Math.sqrt(lengthSquared); + } + + public static void normalize(float[] vector) { + float length = length(vector); + if (length == 0) + return; + length = 1 / length; + for (int i = 0; i < vector.length; i++) + vector[i] *= length; + } + + public static void abs(float[] out, float[] v) { + for (int i = 0; i < out.length; i++) + out[i] = Math.abs(v[i]); + } +} diff --git a/src/main/resources/rs117/hd/frag.glsl b/src/main/resources/rs117/hd/frag.glsl index 556c8ab9d1..01427cb3e3 100644 --- a/src/main/resources/rs117/hd/frag.glsl +++ b/src/main/resources/rs117/hd/frag.glsl @@ -48,6 +48,8 @@ uniform float fogDepth; uniform vec3 waterColorLight; uniform vec3 waterColorMid; uniform vec3 waterColorDark; +uniform float waterTransparencyType; +uniform float waterTransparencyConfig; uniform vec3 ambientColor; uniform float ambientStrength; uniform vec3 lightColor; @@ -65,8 +67,13 @@ uniform int waterHeight; uniform bool waterReflectionEnabled; uniform bool underwaterEnvironment; uniform bool underwaterCaustics; +uniform bool shorelineCaustics; uniform vec3 underwaterCausticsColor; uniform float underwaterCausticsStrength; +uniform int waterCausticsStrengthConfig; +uniform int waterWaveSizeConfig; +uniform int waterWaveSpeedConfig; +uniform int waterFoamAmountConfig; // general HD settings uniform float saturation; @@ -160,13 +167,9 @@ void main() { float mipBias = 0; // Vanilla tree textures rely on UVs being clamped horizontally, // which HD doesn't do, so we instead opt to hide these fragments - if ((vMaterialData[0] >> MATERIAL_FLAG_VANILLA_UVS & 1) == 1) { - blendedUv.x = clamp(blendedUv.x, 0, .984375f); - // Make fishing spots easier to see - if (colorMap1 == MAT_WATER_DROPLETS.colorMap) - mipBias = -100; - } + if ((vMaterialData[0] >> MATERIAL_FLAG_VANILLA_UVS & 1) == 1) + blendedUv.x = clamp(blendedUv.x, 0, .984375f); uv1 = uv2 = uv3 = blendedUv; @@ -176,9 +179,9 @@ void main() { uv3 += material3.scrollDuration * elapsedTime; // Scale from the center - uv1 = (uv1 - .5) * material1.textureScale + .5; - uv2 = (uv2 - .5) * material2.textureScale + .5; - uv3 = (uv3 - .5) * material3.textureScale + .5; + uv1 = (uv1 - .5) * material1.textureScale.xy + .5; + uv2 = (uv2 - .5) * material2.textureScale.xy + .5; + uv3 = (uv3 - .5) * material3.textureScale.xy + .5; // get flowMap map vec2 flowMapUv = uv1 - animationFrame(material1.flowMapDuration); @@ -426,21 +429,39 @@ void main() { vec3 dirLightColor = lightColor * lightStrength; // underwater caustics based on directional light - if (underwaterCaustics && underwaterEnvironment) { - float scale = 12.8; - vec2 causticsUv = worldUvs(scale); + if (underwaterCaustics && underwaterEnvironment) { + float scale = 12.8; + vec2 causticsUv = worldUvs(scale); - const ivec2 direction = ivec2(1, -1); - const int driftSpeed = 231; - vec2 drift = animationFrame(231) * ivec2(1, -2); - vec2 flow1 = causticsUv + animationFrame(19) * direction + drift; - vec2 flow2 = causticsUv * 1.25 + animationFrame(37) * -direction + drift; + const ivec2 direction = ivec2(1, -1); + const int driftSpeed = 231; + vec2 drift = animationFrame(231) * ivec2(1, -2); + vec2 flow1 = causticsUv + animationFrame(19) * direction + drift; + vec2 flow2 = causticsUv * 1.25 + animationFrame(37) * -direction + drift; - vec3 caustics = sampleCaustics(flow1, flow2) * 2; + vec3 caustics = sampleCaustics(flow1, flow2) * 2; - vec3 causticsColor = underwaterCausticsColor * underwaterCausticsStrength; - dirLightColor += caustics * causticsColor * lightDotNormals * pow(lightStrength, 1.5); - } + // make a local copy which we can modify + vec3 underwaterCausticsColor = underwaterCausticsColor; + float underwaterCausticsStrength = underwaterCausticsStrength; + + underwaterCausticsStrength *= 10; + + vec3 absorptionColor = vec3(.003090, .001981, .001548); + + // hard-coded depth + float depth = 128 * 8; + + // Exponential falloff of light intensity when penetrating water, different for each color + vec3 extinctionColors = exp(-depth * absorptionColor); + + underwaterCausticsColor *= extinctionColors; + + // FragColor = vec4(vec3(extinction), 1); return; + + vec3 causticsColor = underwaterCausticsColor * underwaterCausticsStrength; + dirLightColor += caustics * causticsColor * lightDotNormals * pow(lightStrength, 1.5); + } // apply shadows dirLightColor *= inverseShadow; 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 867b5bcb0e..131e8b1e37 100644 --- a/src/main/resources/rs117/hd/opengl/compute/priority_render.cl +++ b/src/main/resources/rs117/hd/opengl/compute/priority_render.cl @@ -41,7 +41,8 @@ void add_face_prio_distance( int map_face_priority(__local struct shared_data *shared, uint localId, struct ModelInfo minfo, int thisPriority, int thisDistance, int *prio); void insert_dfs(__local struct shared_data *shared, uint localId, struct ModelInfo minfo, int adjPrio, int distance, int prioIdx); int tile_height(read_only image3d_t tileHeightMap, int z, int x, int y); -int4 hillskew_vertex(read_only image3d_t tileHeightMap, int4 v, int hillskew, int y, int plane); +void hillskew_vertex(read_only image3d_t tileHeightMap, int4 *v, int hillskew, int y, int plane); +void hillskew_vertexf(read_only image3d_t tileHeightMap, float4 *v, int hillskew, int y, int plane); void undoVanillaShading(int4 *vertex, float3 unrotatedNormal); void sort_and_insert( __local struct shared_data *shared, @@ -242,19 +243,28 @@ int tile_height(read_only image3d_t tileHeightMap, int z, int x, int y) { return read_imagei(tileHeightMap, tileHeightSampler, coord).x << 3; } -int4 hillskew_vertex(read_only image3d_t tileHeightMap, int4 v, int hillskew, int y, int plane) { - if (hillskew == 1) { - int px = v.x & 127; - int pz = v.z & 127; - int sx = v.x >> 7; - int sz = v.z >> 7; +void hillskew_vertex(read_only image3d_t tileHeightMap, int4 *v, int hillskew, int y, int plane) { + int px = v->x & 127; + int pz = v->z & 127; + int sx = v->x >> 7; + int sz = v->z >> 7; int h1 = (px * tile_height(tileHeightMap, plane, sx + 1, sz) + (128 - px) * tile_height(tileHeightMap, plane, sx, sz)) >> 7; int h2 = (px * tile_height(tileHeightMap, plane, sx + 1, sz + 1) + (128 - px) * tile_height(tileHeightMap, plane, sx, sz + 1)) >> 7; int h3 = (pz * h2 + (128 - pz) * h1) >> 7; - return (int4)(v.x, v.y + h3 - y, v.z, v.w); - } else { - return v; - } + v->y += h3 - y; +} + +void hillskew_vertexf(read_only image3d_t tileHeightMap, float4 *v, int hillskew, int y, int plane) { + int x = (int) v->x; + int z = (int) v->z; + int px = x & 127; + int pz = z & 127; + int sx = x >> 7; + int sz = z >> 7; + int h1 = (px * tile_height(tileHeightMap, plane, sx + 1, sz) + (128 - px) * tile_height(tileHeightMap, plane, sx, sz)) >> 7; + int h2 = (px * tile_height(tileHeightMap, plane, sx + 1, sz + 1) + (128 - px) * tile_height(tileHeightMap, plane, sx, sz + 1)) >> 7; + int h3 = (pz * h2 + (128 - pz) * h1) >> 7; + v->y += h3 - y; } void undoVanillaShading(int4 *vertex, float3 unrotatedNormal) { @@ -369,9 +379,11 @@ void sort_and_insert( // apply hillskew int plane = (flags >> 24) & 3; int hillskew = (flags >> 26) & 1; - thisrvA = hillskew_vertex(tileHeightMap, thisrvA, hillskew, minfo.y, plane); - thisrvB = hillskew_vertex(tileHeightMap, thisrvB, hillskew, minfo.y, plane); - thisrvC = hillskew_vertex(tileHeightMap, thisrvC, hillskew, minfo.y, plane); + if (hillskew == 1) { + hillskew_vertex(tileHeightMap, &thisrvA, hillskew, minfo.y, plane); + hillskew_vertex(tileHeightMap, &thisrvB, hillskew, minfo.y, plane); + hillskew_vertex(tileHeightMap, &thisrvC, hillskew, minfo.y, plane); + } // position vertices in scene and write to out buffer vout[outOffset + myOffset * 3] = thisrvA; @@ -398,6 +410,13 @@ void sort_and_insert( uvA.xyz += modelPos; uvB.xyz += modelPos; uvC.xyz += modelPos; + + // For vanilla UVs, the first 3 components are an integer position vector + if (hillskew == 1) { + hillskew_vertexf(tileHeightMap, &uvA, hillskew, minfo.y, plane); + hillskew_vertexf(tileHeightMap, &uvB, hillskew, minfo.y, plane); + hillskew_vertexf(tileHeightMap, &uvC, hillskew, minfo.y, plane); + } } } diff --git a/src/main/resources/rs117/hd/priority_render.glsl b/src/main/resources/rs117/hd/priority_render.glsl index 6ab64e03f7..9732066356 100644 --- a/src/main/resources/rs117/hd/priority_render.glsl +++ b/src/main/resources/rs117/hd/priority_render.glsl @@ -209,10 +209,7 @@ int tile_height(int z, int x, int y) { return texelFetch(tileHeightMap, ivec3(x + ESCENE_OFFSET, y + ESCENE_OFFSET, z), 0).r << 3; } -ivec4 hillskew_vertex(ivec4 v, int hillskew, int y, int plane) { - if (hillskew == 0) - return v; - +void hillskew_vertex(inout ivec4 v, int hillskew, int y, int plane) { int px = v.x & 127; int pz = v.z & 127; int sx = v.x >> 7; @@ -220,7 +217,20 @@ ivec4 hillskew_vertex(ivec4 v, int hillskew, int y, int plane) { int h1 = (px * tile_height(plane, sx + 1, sz) + (128 - px) * tile_height(plane, sx, sz)) >> 7; int h2 = (px * tile_height(plane, sx + 1, sz + 1) + (128 - px) * tile_height(plane, sx, sz + 1)) >> 7; int h3 = (pz * h2 + (128 - pz) * h1) >> 7; - return ivec4(v.x, v.y + h3 - y, v.z, v.w); + v.y += h3 - y; +} + +void hillskew_vertex(inout vec4 v, int hillskew, int y, int plane) { + int x = int(v.x); + int z = int(v.z); + int px = x & 127; + int pz = z & 127; + int sx = x >> 7; + int sz = z >> 7; + int h1 = (px * tile_height(plane, sx + 1, sz) + (128 - px) * tile_height(plane, sx, sz)) >> 7; + int h2 = (px * tile_height(plane, sx + 1, sz + 1) + (128 - px) * tile_height(plane, sx, sz + 1)) >> 7; + int h3 = (pz * h2 + (128 - pz) * h1) >> 7; + v.y += h3 - y; } void undoVanillaShading(inout ivec4 vertex, vec3 unrotatedNormal) { @@ -310,9 +320,11 @@ void sort_and_insert(uint localId, ModelInfo minfo, int thisPriority, int thisDi // apply hillskew int plane = (flags >> 24) & 3; int hillskew = (flags >> 26) & 1; - thisrvA = hillskew_vertex(thisrvA, hillskew, pos.y, plane); - thisrvB = hillskew_vertex(thisrvB, hillskew, pos.y, plane); - thisrvC = hillskew_vertex(thisrvC, hillskew, pos.y, plane); + if (hillskew == 1) { + hillskew_vertex(thisrvA, hillskew, pos.y, plane); + hillskew_vertex(thisrvB, hillskew, pos.y, plane); + hillskew_vertex(thisrvC, hillskew, pos.y, plane); + } // position vertices in scene and write to out buffer vout[outOffset + myOffset * 3] = thisrvA; @@ -338,6 +350,13 @@ void sort_and_insert(uint localId, ModelInfo minfo, int thisPriority, int thisDi uvA.xyz += pos.xyz; uvB.xyz += pos.xyz; uvC.xyz += pos.xyz; + + // For vanilla UVs, the first 3 components are an integer position vector + if (hillskew == 1) { + hillskew_vertex(uvA, hillskew, pos.y, plane); + hillskew_vertex(uvB, hillskew, pos.y, plane); + hillskew_vertex(uvC, hillskew, pos.y, plane); + } } } diff --git a/src/main/resources/rs117/hd/scene/environments.json b/src/main/resources/rs117/hd/scene/environments.json index 373b78566f..15b2955493 100644 --- a/src/main/resources/rs117/hd/scene/environments.json +++ b/src/main/resources/rs117/hd/scene/environments.json @@ -588,6 +588,30 @@ "area": "PYRAMID_PLUNDER", "ambientColor": "#b59b79", "directionalColor": "#8a9eb6", + "directionalStrength": 0.5, + "sunAngles": [ + 80, + 190 + ], + "fogColor": "#190d02", + "fogDepth": 40 + }, + { + "area": "SOPHANEM_TEMPLE_BANK", + "ambientColor": "#b59b79", + "directionalColor": "#8a9eb6", + "directionalStrength": 0.75, + "sunAngles": [ + 80, + 190 + ], + "fogColor": "#190d02", + "fogDepth": 65 + }, + { + "area": "SCARAB_LAIR_TEMPLE", + "ambientColor": "#b59b79", + "directionalColor": "#8a9eb6", "directionalStrength": 0.75, "sunAngles": [ 80, @@ -617,6 +641,24 @@ "fogColor": "#aebde0", "fogDepth": 70 }, + { + "area": "RELLEKKA_SNOWY_REGION", + "ambientColor": "#6fb0ff", + "ambientStrength": 1, + "directionalColor": "#f4e5c9", + "directionalStrength": 2, + "fogColor": "#aebde0", + "fogDepth": 70 + }, + { + "area": "RELLEKKA_HUNTER_ICEBERG", + "ambientColor": "#6fb0ff", + "ambientStrength": 0.75, + "directionalColor": "#f4e5c9", + "directionalStrength": 1, + "fogColor": "#aebde0", + "fogDepth": 70 + }, { "area": "MOUNTAIN_CAMP_ENTRY_PATH", "ambientColor": "#97baff", @@ -875,12 +917,23 @@ { "area": "ZEAH_SNOWY_NORTHERN_REGION", "ambientColor": "#6fb0ff", - "ambientStrength": 1.5, + "ambientStrength": 0.85, "directionalColor": "#f4e5c9", - "directionalStrength": 2.5, + "directionalStrength": 1.5, "fogColor": "#aebde0", "fogDepth": 70 }, + { + "area": "WINTERTODT_ARENA", + "instantTransition": true, + "ambientColor": "#6fb0ff", + "ambientStrength": 1, + "sunAngles": [ 72, 235 ], + "directionalColor": "#f4e5c9", + "directionalStrength": 0.75, + "fogColor": "#aebde3", + "fogDepth": 75 + }, { "area": "ARCEUUS", "ambientColor": "#6369ff", @@ -1460,6 +1513,23 @@ ], "fogDepth": 46 }, + { + "area": "EARTHBOUND_CAVERN", + "sunAngles": [ 62, 210 ], + "waterColor": "#477d95" + }, + { + "area": "RED_CHINCHOMPA_HUNTING_GROUND", + "ambientColor": "#FFFEB0", + "ambientStrength": 0.45, + "directionalColor": "#FFFEB0", + "directionalStrength": 0.65, + "sunAngles": [ + 65, + 102 + ], + "fogDepth": 30 + }, { "key": "OVERWORLD", "area": "OVERWORLD", diff --git a/src/main/resources/rs117/hd/scene/lights.json b/src/main/resources/rs117/hd/scene/lights.json index 60ce5e0c44..baad2234c4 100644 --- a/src/main/resources/rs117/hd/scene/lights.json +++ b/src/main/resources/rs117/hd/scene/lights.json @@ -20014,6 +20014,24 @@ 6202 ] }, + { + "description": "TEXTURED_WALL_TORCH", + "height": 235, + "alignment": "CENTER", + "radius": 300, + "strength": 12.5, + "color": [ + 252, + 148, + 3 + ], + "type": "FLICKER", + "duration": 0, + "range": 20, + "objectIds": [ + 198 + ] + }, { "description": "WALL_TORCH", "height": 235, @@ -20029,13 +20047,11 @@ "duration": 0, "range": 20, "objectIds": [ - 198, 6537, 18891, 12300, 31628, 12238, - 20942, 3474, 43122, 43123, @@ -23712,9 +23728,45 @@ "range": 0, "objectIds": [ 20931, - 20932, - 20948, - 20949 + 20932 + ] + }, + { + "description": "PYRAMID_PLUNDER_WALL_TORCH", + "height": 205, + "offset": [ -15, 0, 0 ], + "radius": 350, + "strength": 12.5, + "color": [ + 252, + 148, + 3 + ], + "type": "FLICKER", + "duration": 0, + "range": 20, + "objectIds": [ + 20942 + ] + }, + { + "description": "Pyramid Plunder Gold Chest Shine", + "height": 150, + "offset": [ 0, 0, 0 ], + "radius": 130, + "strength": 12.5, + "color": [ + 252, + 148, + 3 + ], + "type": "FLICKER", + "duration": 0, + "range": 20, + "objectIds": [ + 26616, + 20946, + 20947 ] }, { @@ -27263,7 +27315,7 @@ "height": 200, "alignment": "CENTER", "radius": 250, - "strength": 0.75, + "strength": 1.25, "color": [ 255, 255, @@ -28959,7 +29011,7 @@ "height": 300, "alignment": "CENTER", "radius": 600, - "strength": 15, + "strength": 10, "color": [ 165, 255, @@ -28968,17 +29020,73 @@ "type": "FLICKER", "duration": 0, "range": 15, + "fadeInDuration": 50, + "fadeOutDuration": 100, "objectIds": [ 29314, 31926 ] }, { - "description": "WINTERTODT_DOOR", + "description": "WINTERTODT_DOOR_INSIDE_1", "height": 500, "alignment": "SOUTH", - "radius": 1500, - "strength": 15, + "radius": 5500, + "strength": 1, + "color": [ + 165, + 255, + 56 + ], + "type": "STATIC", + "duration": 0, + "range": 0, + "objectIds": [ + 29322 + ] + }, + { + "description": "WINTERTODT_DOOR_INSIDE_2", + "height": 500, + "alignment": "SOUTH", + "radius": 500, + "strength": 100, + "color": [ + 165, + 255, + 56 + ], + "type": "STATIC", + "duration": 0, + "range": 0, + "objectIds": [ + 29322 + ] + }, + { + "description": "WINTERTODT_DOOR_OUTSIDE_1", + "height": 500, + "offset": [245,0,0 ], + "radius": 2000, + "strength": 2, + "color": [ + 165, + 255, + 56 + ], + "type": "STATIC", + "duration": 0, + "range": 0, + "objectIds": [ + 29322 + ] + }, + { + "description": "WINTERTODT_DOOR_OUTSIDE_2", + "height": 500, + "offset": [245,0,0 ], + "radius": 500, + "strength": 80, "color": [ 165, 255, @@ -33228,5 +33336,442 @@ "projectileIds": [ 2642 ] + }, + { + "description": "VARLAMORE_ORANGE_BRAZIER", + "height": 150, + "alignment": "CENTER", + "radius": 600, + "strength": 10, + "color": [ + 252, + 148, + 3 + ], + "type": "FLICKER", + "duration": 0, + "range": 30, + "objectIds": [ + 51906, + 51907 + ] + }, + { + "description": "Valamore Colosseum Wall Torch", + "height": 235, + "alignment": "CENTER", + "radius": 275, + "strength": 11.5, + "color": [ + 252, + 148, + 3 + ], + "type": "FLICKER", + "duration": 0, + "range": 20, + "objectIds": [ + 15253 + ] + }, + { + "description": "VALAMORE_FURNACE_GLOW", + "height": 150, + "alignment": "CENTER", + "radius": 300, + "strength": 7.5, + "color": [ + 252, + 90, + 3 + ], + "type": "FLICKER", + "duration": 0, + "range": 10, + "objectIds": [ + 50698 + ] + }, + { + "description": "VALAMORE_OVEN_GLOW", + "height": 120, + "alignment": "CENTER", + "radius": 300, + "strength": 5, + "color": [ + 252, + 148, + 3 + ], + "type": "FLICKER", + "duration": 0, + "range": 20, + "objectIds": [ + 52647, + 52648 + ] + }, + { + "description": "Colosseum northern entrance", + "worldX": 1824, + "worldY": 9521, + "plane": 0, + "radius": 600, + "strength": 10, + "color": "#ffffff", + "offset": [ -64, 160, -64 ] + }, + { + "description": "Colosseum eastern entrance", + "worldX": 1840, + "worldY": 9506, + "plane": 0, + "radius": 600, + "strength": 10, + "color": "#ffffff", + "offset": [ -64, 160, -64 ] + }, + { + "description": "Colosseum western entrance", + "worldX": 1810, + "worldY": 9506, + "plane": 0, + "radius": 600, + "strength": 10, + "color": "#ffffff", + "offset": [ 64, 160, -64 ] + }, + { + "description": "VARLAMORE_CRYPT_AND_CAM_TORUM_HANGING_BRAZIER", + "height": 300, + "alignment": "CENTER", + "radius": 1500, + "strength": 5, + "color": [ + 252, + 148, + 3 + ], + "type": "FLICKER", + "range": 15, + "objectIds": [ + 51578 + ], + "visibleFromOtherPlanes": true + }, + { + "description": "VARLAMORE_TEMPLE_CRYPT_CANDLES", + "height": 50, + "alignment": "CENTER", + "radius": 200, + "strength": 4, + "color": [ + 252, + 148, + 3 + ], + "type": "FLICKER", + "range": 10, + "objectIds": [ + 47446, + 47447 + ], + "visibleFromOtherPlanes": true + }, + { + "description": "CAM_TORUM_TORCH_STATUE", + "offset": [ 0, 138, -38 ], + "radius": 600, + "strength": 15, + "color": [ + 252, + 148, + 3 + ], + "type": "FLICKER", + "range": 7.5, + "objectIds": [ + 51009 + ] + }, + { + "description": "ANTECHAMBER_CEILING_LIGHT", + "worldX": 1440, + "worldY": 9632, + "plane": 1, + "radius": 1600, + "strength": 200, + "color": "#ffffff", + "offset": [ 0, 1820, 0 ] + }, + { + "description": "ANCIENT_SHRINE_CEILING_LIGHT", + "worldX": 1513, + "worldY": 9580, + "plane": 1, + "radius": 1600, + "strength": 100, + "color": "#ffffff", + "offset": [ 0, 1360, 0 ] + }, + { + "description": "ANTECHAMBER_ORANGE_BRAZIERS", + "objectIds": [ 51342 ], + "color": "#e58500", + "strength": 20, + "radius": 1000, + "offset": [ 0, 227, 0 ] + }, + { + "description": "ANTECHAMBER_BLOOD_MOON_BRAZIERS", + "objectIds": [ 51343 ], + "color": "#df002e", + "strength": 10, + "radius": 1000, + "offset": [ 0, 227, 0 ] + }, + { + "description": "ANTECHAMBER_BLUE_MOON_BRAZIERS", + "objectIds": [ 51344 ], + "color": "#3571a7", + "strength": 25, + "radius": 1000, + "offset": [ 0, 227, 0 ] + }, + { + "description": "ANTECHAMBER_ECLIPSE_MOON_BRAZIERS", + "objectIds": [ 51345 ], + "color": "#ffa41b", + "strength": 2, + "radius": 1000, + "offset": [ 0, 227, 0 ] + }, + { + "description": "SULPHUR_NAGA", + "npcIds": [ 13033 ], + "color": "#cd7c00", + "strength": 20, + "radius": 600, + "offset": [ 0, 47, 0 ] + }, + { + "description": "PERILOUS_MOONS_COOKING_STOVE", + "objectIds": [ 51362 ], + "color": "#e58500", + "strength": 30, + "radius": 400, + "offset": [ 0, 42, 0 ] + }, + { + "description": "PERILOUS_MOONS_SURFACE_OPENING_VINES", + "objectIds": [ 51441 ], + "radius": 1300, + "strength": 25, + "color": "#ffffff", + "offset": [ 0, 540, 0 ] + }, + { + "description": "CAM_TORUM_FORGE", + "height": 50, + "alignment": "CENTER", + "radius": 500, + "strength": 12.5, + "color": [ + 252, + 148, + 3 + ], + "type": "FLICKER", + "duration": 0, + "range": 30, + "objectIds": [ + 51496 + ] + }, + { + "description": "EYATLALLI", + "npcIds": [ 12869 ], + "color": "#00c7c1", + "strength": 25, + "radius": 700, + "offset": [ 0, 47, 0 ] + }, + { + "description": "EYATLALLI_RIGHT_HAND", + "npcIds": [ 12869 ], + "color": "#00c7c1", + "strength": 25, + "radius": 250, + "offset": [ 112, 620, 2 ] + }, + { + "description": "EYATLALLI_LEFT_HAND", + "npcIds": [ 12869 ], + "color": "#00c7c1", + "strength": 15, + "radius": 250, + "offset": [ -108, 391, 11 ] + }, + { + "description": "THE_BLUE_MOON_BRAZIERS", + "objectIds": [ 52992, 52993 ], + "color": "#3571a7", + "strength": 25, + "radius": 1000, + "offset": [ 0, 199, -31 ] + }, + { + "description": "ECLIPSE_MOON", + "npcIds": [ 13012, 13019 ], + "color": "#cd7c00", + "strength": 50, + "radius": 1200, + "offset": [ 0, 47, 0 ], + "fadeInDuration": 150 + }, + { + "description": "BLOOD_MOON", + "npcIds": [ 13011 ], + "color": "#df002e", + "strength": 10, + "radius": 1200, + "offset": [ 0, 47, 0 ], + "fadeInDuration": 300, + "spawnDelay": 600 + }, + { + "description": "BLUE_MOON", + "npcIds": [ 13013 ], + "color": "#3571a7", + "strength": 20, + "radius": 1200, + "offset": [ 0, 47, 0 ], + "fadeInDuration": 300, + "spawnDelay": 600 + }, + { + "description": "CURRENT_MOON_PHASE", + "npcIds": [ 13015 ], + "objectIds": [ + 51042 + ], + "color": "#00c7c1", + "strength": 25, + "radius": 320, + "offset": [ 0, 47, 0 ], + "fadeInDuration": 600 + }, + { + "description": "SMALL_MOON_PHASE", + "objectIds": [ 51041 ], + "color": "#00c7c1", + "strength": 25, + "radius": 128, + "offset": [ 0, 47, 0 ], + "fadeInDuration": 1200 + }, + { + "description": "GARDEN_OF_DEATH_DUNGEON_ENTRANCE", + "height": 370, + "alignment": "CENTER", + "radius": 650, + "strength": 15, + "color": [ + 200, + 220, + 255 + ], + "type": "STATIC", + "duration": 0, + "range": 0, + "objectIds": [ + 46327, + 46330, + 46333, + 46336 + ] + }, + { + "description": "Hunter Guild ceiling lantern", + "radius": 600, + "strength": 5, + "color": "#fcb703", + "type": "FLICKER", + "range": 13, + "offset": [ 0, 406, 0 ], + "objectIds": [ + 51731 + ] + }, + { + "description": "Red Chinchompa Hunting Grounds Exit", + "offset": [ 6, 40, 36 ], + "radius": 350, + "strength": 35, + "color": "#ffffff", + "type": "FLICKER", + "range": 13, + "objectIds": [ + 19037 + ] + }, + { + "description": "Tutorial Island Cooking Range", + "height": 100, + "offset": [ 53, -53, 48 ], + "radius": 400, + "strength": 6, + "color": [ + 252, + 148, + 3 + ], + "type": "FLICKER", + "duration": 0, + "range": 5, + "objectIds": [ + 9736 + ] + }, + { + "description": "Tutorial Island Ironman Portal", + "height": 100, + "offset": [ -6, 28, -20 ], + "radius": 400, + "strength": 20, + "color": [ + 3, + 50, + 252 + ], + "type": "PULSE", + "duration": 1000, + "range": 12, + "objectIds": [ + 42819 + ] + }, + { + "description": "Easter event 2024 brazier", + "objectIds": [ 53050 ], + "radius": 500, + "strength": 20, + "color": "#dc5600", + "type": "FLICKER", + "range": 10, + "offset": [ 0, 208, 0 ] + }, + { + "description": "Hunter Guild fire", + "radius": 200, + "strength": 15, + "color": "#fc9403", + "type": "FLICKER", + "duration": 600, + "range": 15, + "offset": [ 0, 52, 0 ], + "objectIds": [ + 51728 + ] } ] diff --git a/src/main/resources/rs117/hd/scene/model_overrides.json b/src/main/resources/rs117/hd/scene/model_overrides.json index 23b938ff4b..485b2867a5 100644 --- a/src/main/resources/rs117/hd/scene/model_overrides.json +++ b/src/main/resources/rs117/hd/scene/model_overrides.json @@ -928,7 +928,6 @@ 4502, 5252, 6188, - 6202, 10436, 10487, 11468, @@ -1326,6 +1325,7 @@ 5903, 5904, 5905, + 9733, 14513, 14514, 14515, @@ -1346,6 +1346,7 @@ "baseMaterial": "BARK", "objectIds": [ 1341, + 1353, 4328, 6212, 14516, @@ -1442,10 +1443,21 @@ "baseMaterial": "GRUNGE_1", "objectIds": [ 7522, - 7523 + 7523, + 46420 ], "uvType": "MODEL_XZ" }, + { + "description": "Farming Patch 3 - Keldagrim", + "baseMaterial": "DIRT_2", + "objectIds": [ + 8861, 8862, 8863, 8864, 8876 + ], + "uvType": "MODEL_XZ", + "uvOrientation": 55, + "uvScale": 0.75 + }, { "description": "GRASS", "baseMaterial": "GRAY_75", @@ -1459,7 +1471,6 @@ 4987, 4988, 5341, - 5536, 9502, 9503, 9504, @@ -1543,6 +1554,7 @@ 5340, 5342, 5338, + 5536, 6817, 6818, 6819, @@ -1552,7 +1564,6 @@ 6838, 7049, 7050, - 7129, 7262, 7263, 7264, @@ -1613,16 +1624,58 @@ "description": "Short grass which shouldn't cast shadows and shouldn't inherit tile color", "baseMaterial": "GRAY_75", "objectIds": [ + 7129, 7518, + 7519, 7520, + 7521, 17446, 17447, 17448, 17449, 34494, - 34496 + 34496, + 46411, + 46412 + ], + "flatNormals": true, + "castShadows": false + }, + { + "description": "Valamore, Fortis and Avium Grass", + "baseMaterial": "GRAY_75", + "objectIds": [ + 51804, + 51805, + 51806, + 51808, + 51809, + 51807, + 51810, + 51811, + 51812, + 51813, + 51814, + 51815, + 51816, + 51817 + ], + "flatNormals": true, + "castShadows": false + }, + { + "description": "Short grass which shouldn't cast shadows but should inherit tile color, this is required to make the grass around the edges white and not green in winter", + "baseMaterial": "GRAY_75", + "seasonalTheme": "WINTER", + "objectIds": [ + 7129, + 7518, + 7519, + 7520, + 7521 ], "flatNormals": true, + "inheritTileColorType": "UNDERLAY", "castShadows": false }, { @@ -1643,14 +1696,16 @@ 16825, 16826 ], - "flatNormals": true + "flatNormals": true, + "castShadows": false }, { "description": "WOODEN_FENCES", - "baseMaterial": "WOOD_GRAIN", + "baseMaterial": "WOOD_GRAIN_3", "objectIds": [ 166, 167, + 980, 981, 991, 992, @@ -1663,6 +1718,8 @@ 7055, 9511, 9623, + 9470, + 9708, 25677, 1558, 1559, @@ -1677,6 +1734,9 @@ 993, 7527 ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 512, "flatNormals": true }, { @@ -2542,10 +2602,14 @@ 14550, 14551, 14558, + 14559, + 14560, + 14632, 42110, 42111, 42116 ], + "uvType": "BOX", "flatNormals": true }, { @@ -2589,6 +2653,133 @@ ], "flatNormals": true }, + { + "description": "Wintertodt Terrain", + "baseMaterial": "SNOW_4", + "objectIds": [ + 29297, + 29293, + 29296, + 29295, + 29298, + 29294, + 29279, + 29265, + 29280, + 29265, + 15258, + 15451, + 29135, + 29134, + 29281, + 29282, + 29286, + 29285, + 29278, + 29283, + 29284, + 29266 + ], + "uvType": "BOX", + "uvScale": 1.25 + }, + { + "description": "Wintertodt Railings", + "baseMaterial": "SNOW_4", + "objectIds": [ + 29288, + 29287, + 29289, + 29290, + 29291 + ], + "uvType": "BOX", + "uvScale": 1.25 + }, + { + "description": "Wintertodt Wooden Objects", + "baseMaterial": "WOOD_GRAIN_2_SMOOTH_LIGHT", + "objectIds": [ + 29319, + 29320, + 29317, + 29318, + 29316, + 29301, + 29305, + 29302, + 29321, + 26330 + ], + "uvType": "WORLD_XY", + "uvScale": 1.5 + }, + { + "description": "Wintertodt Wooden Objects rotated", + "baseMaterial": "WOOD_GRAIN_2_SMOOTH_LIGHT", + "objectIds": [ + 29303, + 29304 + ], + "uvType": "WORLD_XY", + "uvScale": 1.5 + }, + { + "description": "Wintertodt Bruma Roots", + "baseMaterial": "VERY_LIGHT_BARK", + "objectIds": [ + 29311 + ], + "uvType": "BOX" + }, + { + "description": "Wintertodt Herb Roots", + "baseMaterial": "LIGHT_BARK", + "objectIds": [ + 29315 + ], + "uvType": "BOX" + }, + { + "description": "Wintertodt Brazier", + "baseMaterial": "METALLIC_2_HIGHGLOSS", + "objectIds": [ + 29312, + 29313, + 29314 + ], + "uvType": "BOX" + }, + { + "description": "Wintertodt Tents", + "baseMaterial": "FINE_CARPET", + "objectIds": [ + 29307, + 29306, + 17359, + 17360, + 17361 + ], + "uvType": "BOX" + }, + { + "description": "Wintertodt Wooden Cart", + "baseMaterial": "WOOD_GRAIN_2_SMOOTH", + "objectIds": [ + 27364 + ], + "uvType": "BOX", + "uvOrientation": 512, + "uvScale": 1.5 + }, + { + "description": "Wintertodt Snow Pile with Wooden Wheel", + "baseMaterial": "SNOW_4", + "objectIds": [ + 26314 + ], + "uvType": "BOX" + }, { "description": "STATUE_BUST", "baseMaterial": "MARBLE_1_GLOSS", @@ -3179,12 +3370,24 @@ 9739, 9740, 16543, + 17086, + 17087, 26712 ], "uvType": "BOX", "uvScale": 0.75, "uvOrientation": 100 }, + { + "description": "Rounded Cave Wall Entrance with Tracks", + "baseMaterial": "GRUNGE_1", + "objectIds": [ + 6971 + ], + "uvType": "BOX", + "uvScale": 0.75, + "uvOrientation": 100 + }, { "description": "ROUNDED_CAVE_WALLS_TOP", "baseMaterial": "GRUNGE_1", @@ -3384,7 +3587,23 @@ 43700, 43849 ], - "baseMaterial": "UNLIT" + "baseMaterial": "UNLIT", + "castShadows": false + }, + { + "description": "Guardians of the Rift barriers", + "objectIds": [ + 43744, + 43745, + 43746, + 43747, + 43748, + 43749, + 43750, + 43751 + ], + "baseMaterial": "UNLIT", + "castShadows": false }, { "description": "Tombs of Amascut Baba boulders", @@ -3457,6 +3676,90 @@ 20976, 20977, 20978 ] }, + { + "description": "Pyramid Plunder Walls - Interior", + "baseMaterial": "ROCK_3", + "objectIds": [ + 20932, + 20933, + 20939, + 20937, + 20948, + 20949, + 20931, + 21280, + 20934, + 20944, + 20943, + 20945 + ], + "uvType": "BOX" + }, + { + "description": "Pyramid Plunder Obelisk, Busts and Wall symbol", + "baseMaterial": "MARBLE_1_GLOSS", + "objectIds": [ + 20955, + 20954, + 20953, + 20941 + ], + "uvType": "BOX", + "uvScale": 1.2 + }, + { + "description": "Pyramid Plunder Metallic Objects", + "baseMaterial": "METALLIC_2_SUPER_HIGHGLOSS", + "objectIds": [ + 20946, + 20947, + 21263, + 21267, + 21262, + 21266, + 21261, + 21265 + ], + "uvType": "BOX", + "uvScale": 1.5 + }, + { + "description": "Pyramid Plunder Wall Torch", + "baseMaterial": "MARBLE_1", + "objectIds": [ + 20942 + ], + "uvType": "BOX" + }, + { + "description": "Pyramid Plunder Sarcophagus", + "baseMaterial": "WOOD_GRAIN_2_SMOOTH", + "objectIds": [ + 21255, + 21257 + ], + "uvType": "BOX", + "uvScale": 1.2, + "uvOrientation": 1142 + }, + { + "description": "Pyramid Plunder Sarcophagus Opened", + "baseMaterial": "WOOD_GRAIN_2_SMOOTH", + "objectIds": [ + 21256, + 21259 + ], + "uvType": "BOX", + "uvScale": 1.2, + "uvOrientation": 1012 + }, + { + "description": "Pyramid Plunder Pot", + "baseMaterial": "WOOD_GRAIN_2", + "objectIds": [ + 21268 + ] + }, { "description": "Lighthouse underground door", "objectIds": [ @@ -3504,6 +3807,10 @@ 801, 802, 803, + 804, + 805, + 806, + 807, 808, 809, 810, @@ -3776,6 +4083,7 @@ 24561, 25796, 29110, + 42114, 44917 ], "flatNormals": true, @@ -4105,13 +4413,6 @@ "uvType": "WORLD_XZ", "flatNormals": true }, - { - "description": "Brass coat rack", - "baseMaterial": "NONE", - "objectIds": [ - 374 - ] - }, { "description": "Dirt Cave Entrance", "baseMaterial": "DIRT_2", @@ -4128,7 +4429,8 @@ 26709, 30177, 30374, - 31766 + 31766, + 42248 ], "uvType": "BOX", "uvScale": 0.5 @@ -4226,9 +4528,6 @@ 12575, 12576, 12577, - 11184, - 11185, - 11186, 18479, 18480, 37810, @@ -4304,6 +4603,10 @@ 2148, 2884, 8785, + 9725, + 9726, + 9727, + 9728, 16679, 16683, 16684, @@ -4455,7 +4758,6 @@ "objectIds": [ 10490, 12450, - 12962, 14762, 14851, 14852, @@ -4482,6 +4784,7 @@ "baseMaterial": "WOOD_GRAIN_2", "objectIds": [ 7175, + 12962, 20058, 23177, 39716 @@ -4782,6 +5085,8 @@ 8820, 8821, 9565, + 9717, + 9718, 11449, 11450, 11721, @@ -5002,7 +5307,8 @@ "objectIds": [ 2341, 14543 - ] + ], + "uvType": "BOX" }, { "description": "Black Knight Fortress Wood Rails", @@ -5071,17 +5377,10 @@ "baseMaterial": "METALLIC_2_GLOSS", "objectIds": [ 205, + 3474, 24172 ] }, - { - "description": "Butler's House", - "baseMaterial": "DOCK_FENCE", - "objectIds": [ - 980 - ], - "flatNormals": true - }, { "description": "Wooden Door 1 - Fake Plank", "baseMaterial": "WOOD_GRAIN_3", @@ -5096,6 +5395,7 @@ 102, 1535, 1536, + 1539, 1732, 1722, 1726, @@ -5154,6 +5454,7 @@ 9710, 9716, 9721, + 9722, 9723, 9724, 10045, @@ -5171,6 +5472,8 @@ 11776, 11777, 11788, + 12349, + 12350, 13001, 13002, 14749, @@ -5200,6 +5503,7 @@ 24958, 25417, 25418, + 25637, 25716, 25717, 25819, @@ -5820,6 +6124,19 @@ 17874, 17875, 19215, + 19216, + 19217, + 19218, + 19219, + 19851, + 20129, + 20130, + 20131, + 20648, + 20649, + 20650, + 20651, + 20652, 20753, 20754, 20755, @@ -5858,7 +6175,9 @@ 42373, 42374, 42675, - 42376 + 42376, + 50725, + 50726 ], "uvType": "BOX", "uvScale": 0.32 @@ -6011,6 +6330,7 @@ "baseMaterial": "DIRT_2", "objectIds": [ 4343, + 5520, 17227 ], "uvType": "WORLD_XZ", @@ -6043,6 +6363,8 @@ 321, 4342, 4344, + 5519, + 5521, 11084, 11085, 11086, @@ -6235,7 +6557,6 @@ 7113, 7114, 7115, - 7116, 7119, 12049, 12051, @@ -6506,6 +6827,8 @@ 25661, 25836, 25837, + 41738, + 42066, 44854 ], "uvType": "BOX", @@ -6521,6 +6844,9 @@ 6176, 6177, 9533, + 12977, + 12978, + 17033, 20367, 25775, 25776, @@ -6545,10 +6871,12 @@ "uvOrientationZ": 512 }, { - "description": "Objects - Wooden - Crate", + "description": "Objects - Wooden - Crate - Angled", "baseMaterial": "WOOD_GRAIN_3", "objectIds": [ - 9535 + 9535, + 41739, + 52432 ], "uvType": "BOX", "flatNormals": true, @@ -6578,6 +6906,18 @@ "uvOrientationX": 512, "uvOrientationZ": 512 }, + { + "description": "Objects - Wooden - Stack of crates - Rotated", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 42068 + ], + "uvType": "BOX", + "flatNormals": true, + "uvOrientationY": 88, + "uvOrientationX": 512, + "uvOrientationZ": 512 + }, { "description": "Objects - Wooden - Boxes", "baseMaterial": "WOOD_GRAIN_3", @@ -6772,8 +7112,12 @@ 5232, 17876, 17877, + 42263, + 42264, 44744, - 44745 + 44745, + 46408, + 46409 ], "uvType": "BOX" }, @@ -6958,7 +7302,9 @@ "objectIds": [ 4055, 4056, - 4057 + 4057, + 46344, + 46346 ], "uvType": "BOX", "uvScale": 0.6 @@ -7315,7 +7661,8 @@ 422, 423, 4288, - 23067 + 23067, + 27664 ], "uvType": "MODEL_XZ", "uvScale": 0.60 @@ -7591,11 +7938,21 @@ "baseMaterial": "WOOD_GRAIN_3", "objectIds": [ 12884, + 12972, 16974, 24333 ], "uvType": "BOX" }, + { + "description": "Objects - Wooden - Plank tables - Long", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 12971 + ], + "uvType": "BOX", + "uvOrientation": 512 + }, { "description": "Objects - Wooden - Plank tables - Wide", "baseMaterial": "WOOD_GRAIN_2_WIDE", @@ -7634,31 +7991,31 @@ "objectIds": [ 16740 ], - "uvType": "MODEL_XZ", + "uvType": "BOX", "flatNormals": true, "uvOrientation": 512, "uvScale": 0.8 }, { "description": "Lunar Isles - Objects - Wooden staircase", - "baseMaterial": "HD_WOOD_PLANKS_1", + "baseMaterial": "HD_WOOD_PLANKS_2", "objectIds": [ 16732, 16733, 16734 ], - "uvType": "MODEL_XZ" + "uvType": "BOX" }, { "description": "Lunar Isles - WALLS - Wooden Railing", - "baseMaterial": "HD_WOOD_PLANKS_1", + "baseMaterial": "HD_WOOD_PLANKS_2", "objectIds": [ 16761, 16762, 16763, 16767 ], - "uvType": "MODEL_XZ", + "uvType": "BOX", "flatNormals": true }, { @@ -7846,7 +8203,11 @@ 37396, 46818, 46819, - 46820 + 46820, + 47161, + 47162, + 47163, + 47164 ], "uvType": "BOX", "uvScale": 0.33 @@ -7936,6 +8297,23 @@ "uvType": "MODEL_XZ", "uvScale": 0.6 }, + { + "description": "Objects - Wooden - Rocking Chair", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 1096, + 15681 + ], + "uvType": "BOX" + }, + { + "description": "Objects - Wooden - Rocking Chair - Combined back", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 12979 + ], + "uvType": "BOX" + }, { "description": "Objects - Wooden - Chair - No cushion", "baseMaterial": "WOOD_GRAIN_3", @@ -7954,12 +8332,19 @@ "uvOrientation": 512, "uvScale": 0.6 }, + { + "description": "Objects - Wooden - Chair - No cushion - Smashed", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ 1089 ], + "uvType": "BOX", + "uvOrientation": 512, + "uvScale": 0.6 + }, { "description": "Objects - Wooden - Chair 2", "baseMaterial": "WOOD_GRAIN_3", "objectIds": [ 7182, - 15681, 15682 ], "uvType": "BOX" @@ -7968,6 +8353,7 @@ "description": "Objects - Wooden - Chair - Plank Highback", "baseMaterial": "WOOD_GRAIN_3", "objectIds": [ + 12973, 20352 ], "uvType": "BOX", @@ -8021,7 +8407,7 @@ 16667, 16668 ], - "uvType": "MODEL_XZ", + "uvType": "BOX", "uvOrientation": 512, "uvScale": 0.4 }, @@ -9195,6 +9581,7 @@ "description": "Metal Birdcage", "baseMaterial": "METALLIC_1_SEMIGLOSS", "objectIds": [ + 6146, 6264, 6265, 6266, @@ -9760,6 +10147,16 @@ "uvOrientation": 256, "uvScale": 0.6 }, + { + "description": "Sacks - Grey", + "baseMaterial": "CARPET", + "objectIds": [ + 10304, 10305 + ], + "uvType": "BOX", + "uvOrientation": 256, + "uvScale": 0.6 + }, { "description": "Sacks Pile", "baseMaterial": "CARPET", @@ -9798,7 +10195,7 @@ { "description": "Object - Sack with Hey or Grain", "baseMaterial": "FINE_CARPET", - "objectIds": [ 16878 ], + "objectIds": [ 5574, 10306, 16878 ], "uvType": "BOX", "uvScale": 0.85, "retainVanillaUvs": false @@ -9816,7 +10213,7 @@ "objectIds": [ 17955 ], - "uvType": "MODEL_XY", + "uvType": "BOX", "uvOrientation": 512, "uvScale": 0.77 }, @@ -9953,7 +10350,7 @@ 18117, 18118 ], - "uvType": "MODEL_XZ" + "uvType": "BOX" }, { "description": "Meiyerditch Wall Plank Roofs", @@ -9964,7 +10361,7 @@ 17711, 17712 ], - "uvType": "MODEL_XZ" + "uvType": "BOX" }, { "description": "Meiyerditch Wall Plank Floors - Kicked", @@ -9972,8 +10369,9 @@ "objectIds": [ 18036 ], - "uvType": "MODEL_XZ", - "uvScale": 0.66 + "uvType": "BOX", + "uvScale": 0.66, + "uvOrientation": 512 }, { "description": "Meiyerditch Plank Door", @@ -10049,7 +10447,7 @@ 18063, 18064 ], - "uvType": "MODEL_XZ", + "uvType": "BOX", "uvScale": 0.6 }, { @@ -10058,7 +10456,7 @@ "objectIds": [ 17896 ], - "uvType": "MODEL_XZ", + "uvType": "BOX", "uvOrientation": 512, "uvScale": 0.6 }, @@ -10068,7 +10466,7 @@ "objectIds": [ 32577 ], - "uvType": "MODEL_XZ", + "uvType": "BOX", "uvScale": 0.86 }, { @@ -10078,7 +10476,7 @@ 17548, 17549 ], - "uvType": "MODEL_XZ", + "uvType": "BOX", "uvOrientation": 512, "uvScale": 0.6 }, @@ -10116,8 +10514,8 @@ 18107, 18108 ], - "uvType": "MODEL_XZ", - "uvOrientation": 512, + "uvType": "BOX", + "uvOrientationY": 512, "uvScale": 0.86 }, { @@ -10138,7 +10536,7 @@ 17644, 17645 ], - "uvType": "MODEL_XY_MIRROR_A", + "uvType": "BOX", "uvOrientation": 512, "uvScale": 0.32 }, @@ -10191,7 +10589,7 @@ 18049, 18050 ], - "uvType": "GEOMETRY" + "uvType": "BOX" }, { "description": "Meiyerditch Myreque Hideout Brick Stone Walls", @@ -10959,11 +11357,13 @@ }, { "description": "Wooden Bank Chest", - "baseMaterial": "WOOD_GRAIN", + "baseMaterial": "WOOD_GRAIN_3", "objectIds": [ + 2090, 26711 ], - "uvType": "GEOMETRY" + "uvType": "BOX", + "uvOrientation": 512 }, { "description": "Ferox Enclave - Wooden Plank Stairs ", @@ -11044,6 +11444,7 @@ "description": "Wooden Wedge Stairs", "baseMaterial": "WOOD_GRAIN_3", "objectIds": [ + 1863, 24361 ], "uvType": "BOX", @@ -11361,11 +11762,12 @@ ] }, { - "description": "Lassar Undercity fake tiles", - "objectIds": [ 46950 ], + "description": "Lassar Undercity & Varlamore temple crypt fake brick tiles", + "objectIds": [ 46950, 46951 ], "baseMaterial": "LASSAR_UNDERCITY_TILES", "hideInAreas": [ - "LASSAR_UNDERCITY" + "LASSAR_UNDERCITY", + "VARLAMORE_TEMPLE_CRYPT" ] }, { @@ -11557,6 +11959,7 @@ "description": "Ore - Tin - Rock", "baseMaterial": "ROCK_3_ORE", "objectIds": [ + 10080, 11360, 11361 ], @@ -11567,6 +11970,7 @@ "description": "Ore - Copper - Rock", "baseMaterial": "ROCK_3_ORE", "objectIds": [ + 10079, 10943, 11161 ], @@ -11578,7 +11982,8 @@ "baseMaterial": "ROCK_3", "objectIds": [ 11364, - 11365 + 11365, + 42833 ], "uvType": "BOX", "uvScale": 0.33, @@ -11917,7 +12322,8 @@ "objectIds": [ 1323, 1324, - 1325 + 1325, + 1458 ], "uvType": "BOX", "uvScale": 0.6, @@ -12336,28 +12742,18 @@ "retainVanillaUvs": false }, { - "description": "Objects - Hay - Mat", + "description": "Objects - Hay - Haystack", "textureMaterial": "HD_HAY", "objectIds": [ - 920 + 300 ], "uvType": "BOX", + "uvScale": 0.47, "uvOrientation": 512, "retainVanillaUvs": false }, { - "description": "Objects - Hay - Haystack", - "textureMaterial": "HD_HAY", - "objectIds": [ - 300 - ], - "uvType": "BOX", - "uvScale": 0.47, - "uvOrientation": 512, - "retainVanillaUvs": false - }, - { - "description": "Objects - Hay - Hay Bale", + "description": "Objects - Hay - Hay Bale", "textureMaterial": "HD_HAY", "objectIds": [ 298, @@ -12370,7 +12766,7 @@ "retainVanillaUvs": false }, { - "description": "Edgeville - Walls - Bank Front Windows", + "description": "Edgeville - Walls - Bank Front Windows - Tutorial Island Walls", "objectIds": [ 1853, 1854 @@ -14865,7 +15261,7 @@ "description": "Keldagrim Stone Walls", "baseMaterial": "ROCK_3", "objectIds": [ - 6000, 6001, 6002, 6003, 6004, 6005, 6006, 6010, 6011 + 6000, 6001, 6002, 6003, 6004, 6005, 6006, 6010, 6011, 8881, 8882, 8883, 8884, 8905, 8907, 8909 ], "uvType": "BOX", "uvOrientation": 128 @@ -15017,6 +15413,16 @@ "uvType": "BOX", "uvScale": 0.8 }, + { + "description": "Objects - Rock Column - Tan - Huge", + "baseMaterial": "ROCK_3", + "objectIds": [ + 11184, 11185, 11186 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 132 + }, { "description": "Objects - Stone - Stone Slab Bridge Guards", "baseMaterial": "ROCK_3", @@ -15040,7 +15446,7 @@ "description": "Objects - Rock - Unfinished Statues", "baseMaterial": "ROCK_5", "objectIds": [ - 7008, 7009 + 7008, 7009, 7010 ], "uvType": "BOX", "uvScale": 0.8 @@ -15049,6 +15455,7 @@ "description": "Blast Furnace - Objects - Metallic - Sink", "baseMaterial": "METALLIC_2", "objectIds": [ + 6151, 9143 ], "uvType": "BOX", @@ -15090,5 +15497,882 @@ ], "uvType": "BOX", "uvScale": 0.86 + }, + { + "description": "Valamore - quetzal landing site", + "baseMaterial": "HAY", + "objectIds": [ + 52815 + ], + "uvType": "BOX", + "uvScale": 0.6 + }, + { + "description": "Valamore Rugs and carpets", + "baseMaterial": "FINE_CARPET", + "objectIds": [ + 52467, + 52468, + 52469, + 52470, + 52471, + 52472, + 52473, + 52474, + 52475, + 52476, + 52573, + 52574, + 52575 + ], + "uvType": "MODEL_XZ" + }, + { + "description": "Valamore Banners", + "baseMaterial": "FINE_CARPET", + "objectIds": [ + 51946, + 51947, + 51948, + 52314 + ], + "uvType": "MODEL_YZ" + }, + { + "description": "Hunter Guild cave walls", + "baseMaterial": "ROCK_3", + "objectIds": [ + 42251, + 42266, + 42267, + 42268, + 42269, + 42270, + 42272, + 50906, + 50907, + 50908, + 50909, + 50910, + 50911, + 50922, + 51405 + ], + "uvType": "BOX", + "uvScale": 0.86 + }, + { + "description": "Hunter Guild Entrance Bone Arches", + "baseMaterial": "BONE", + "objectIds": [ + 51657, + 51658 + ], + "uvType": "BOX", + "uvOrientation": 512 + }, + { + "description": "Hunter Guild Wooden Fence Walls", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 51639, + 51663, + 51668, + 51672 + ], + "uvType": "BOX" + }, + { + "description": "Hunter Guild Big Tree", + "baseMaterial": "BARK", + "objectIds": [ + 51619, + 51620, + 51621, + 51622, + 51623, + 51624, + 51625, + 51627, + 51628, + 51629, + 51630, + 51631, + 51632, + 51632, + 51633, + 51634, + 51635, + 51636, + 51637, + 51638, + 51641, + 51646, + 51646, + 51647, + 51648, + 51649, + 51650, + 51651, + 51652, + 51653, + 51654, + 51655, + 51656 + ], + "uvType": "BOX", + "uvScale": 1.2 + }, + { + "description": "Hunter Guild Big Tree Rope", + "baseMaterial": "FINE_CARPET", + "objectIds": [ + 51643 + ], + "uvType": "BOX", + "uvScale": 0.75 + }, + { + "description": "Hunter Guild blue rug", + "baseMaterial": "GRUNGE_2", + "objectIds": [ + 51737, + 51738, + 51739, + 51740, + 51741, + 51742, + 51743, + 51744 + ], + "uvType": "WORLD_XZ", + "uvScale": 0.3 + }, + { + "description": "Hunter Guild Cavern Counter", + "baseMaterial": "WOOD_GRAIN_2", + "objectIds": [ + 51718, + 51719, + 51720, + 51721 + ], + "uvType": "BOX", + "uvOrientationY": 512 + }, + { + "description": "Hunter Guild Wooden Objects", + "baseMaterial": "WOOD_GRAIN_2", + "objectIds": [ + 51745, + 52425, + 52430 + ], + "uvType": "BOX", + "uvOrientation": 512 + }, + { + "description": "Hunter Guild Caverns Stairs", + "baseMaterial": "WOOD_GRAIN_2", + "objectIds": [ + 51642 + ], + "uvType": "BOX", + "uvScale": 1.5 + }, + { + "description": "Hunter Pitfall Spiked Pit - Snowy", + "baseMaterial": "SNOW_2", + "objectIds": [ + 19249, + 19250, + 19251, + 19252 + ], + "uvType": "BOX", + "uvScale": 1.2 + }, + { + "description": "Bear Rugs", + "baseMaterial": "FINE_CARPET", + "objectIds": [ + 950, + 951, + 952, + 953 + ], + "uvType": "MODEL_XZ", + "uvScale": 1.5 + }, + { + "description": "Obelisk of Earth", + "objectIds": [ 2150 ], + "baseMaterial": "ROCK_5", + "uvType": "BOX", + "uvOrientation": 700, + "flatNormals": true + }, + { + "description": "Obelisk of Water", + "objectIds": [ 2151 ], + "flatNormals": true, + "retainVanillaUvs": false, + "materialOverrides": { + "WATER_FLAT": { + "textureMaterial": "TRANSPARENT", + "uvType": "WORLD_XZ", + "uvScale": 1, + "retainVanillaUvs": false + } + } + }, + { + "description": "Obelisk of Air", + "objectIds": [ 2152 ], + "flatNormals": true, + "receiveShadows": false + }, + { + "description": "Obelisk of Fire", + "objectIds": [ 2153 ], + "flatNormals": true, + "retainVanillaUvs": false, + "materialOverrides": { + "LAVA": { + "textureMaterial": "HD_MAGMA_2", + "uvType": "WORLD_XZ", + "retainVanillaUvs": false + } + } + }, + { + "description": "Objects - Stone - Keldagrim Pillars - Twisted", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 7016 + ], + "uvType": "BOX", + "uvScale": 0.86, + "uvOrientation": 66 + }, + { + "description": "Objects - Stone - Keldagrim Pillars - Straight", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 7017 + ], + "uvType": "BOX", + "uvScale": 0.86, + "uvOrientation": 300 + }, + { + "description": "Metal Street Lanturn", + "baseMaterial": "METALLIC_2_SEMIGLOSS", + "objectIds": [ + 6202 + ], + "uvType": "BOX", + "uvOrientation": 96, + "uvScale": 0.6, + "hideVanillaShadows": true + }, + { + "description": "Keldagrim Fireplace", + "baseMaterial": "METALLIC_2", + "objectIds": [ + 6093 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 42 + }, + { + "description": "Keldagrim Fireplace with Spit - Empty", + "baseMaterial": "METALLIC_2", + "objectIds": [ + 6094 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 42 + }, + { + "description": "Keldagrim Fireplace with Spit and Meat", + "baseMaterial": "METALLIC_2", + "objectIds": [ + 6095 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 42 + }, + { + "description": "White Wolf Mountain Wall Supports", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 17082, 17083, 17084, 17085 + ], + "uvType": "BOX", + "uvScale": 0.6, + "uvOrientation": 88 + }, + { + "description": "Wooden Cupboard Empty", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 7018 + ], + "uvType": "BOX", + "uvScale": 0.75, + "uvOrientation": 22 + }, + { + "description": "Wooden Cabinet Empty", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 7019 + ], + "uvType": "BOX", + "uvScale": 0.75, + "uvOrientation": 2022 + }, + { + "description": "Rat Wall", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 10342, 10343 + ], + "uvType": "BOX", + "uvScale": 1.1, + "uvOrientation": 512 + }, + { + "description": "Strange Box - Metal", + "baseMaterial": "METALLIC_2", + "objectIds": [ + 8879, 8880 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 12 + }, + { + "description": "Scaffolding - Metal", + "baseMaterial": "METALLIC_2", + "objectIds": [ + 8899, 8900, 8901, 8902, 8903, 8904 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 346 + }, + { + "description": "Struts - Metal", + "baseMaterial": "METALLIC_2", + "objectIds": [ + 8917 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 346 + }, + { + "description": "Zeah Battlefield - Objects - Stone - Pillars", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 34358 + ], + "uvType": "BOX", + "uvScale": 0.75 + }, + { + "description": "Zeah Battlefield - Objects - Stone - Tiles", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 23106 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 220 + }, + { + "description": "Zeah Battlefield - Objects - Stone - Ancient Grave", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 33579 + ], + "uvType": "BOX", + "uvScale": 0.6, + "uvOrientationZ": 560, + "uvOrientationY": 1000 + }, + { + "description": "Zeah Battlefield - Objects - Stone - Ornate Fountain", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 10827 + ], + "uvType": "BOX", + "uvScale": 0.6, + "uvOrientationZ": 560, + "uvOrientationY": 1000 + }, + { + "description": "Zeah Battlefield - Walls - Metal Fence", + "baseMaterial": "METALLIC_1", + "objectIds": [ + 27926 + ], + "uvType": "BOX" + }, + { + "description": "Zeah - Rough Plank Walls", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 8695, 8704 + ], + "uvType": "BOX", + "uvOrientation": 1024, + "uvScale": 1.1 + }, + { + "description": "Zeah - Rough Plank Roofs", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 8705, 8706, 8707 + ], + "uvType": "BOX", + "uvOrientationY": 1024, + "uvOrientationX": 512, + "uvScale": 1.1 + }, + { + "description": "Zeah - Rough Plank Roofs - Corners", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 8708, 8709, 8710, 8711 + ], + "uvType": "BOX", + "uvOrientationX": 1024, + "uvOrientationZ": 512, + "uvOrientationY": 512, + "uvScale": 1.1 + }, + { + "description": "Objects - Metal Milk Urn", + "baseMaterial": "METALLIC_2", + "objectIds": [ + 8690 + ], + "uvType": "BOX", + "uvScale": 0.75, + "uvOrientation": 42 + }, + { + "description": "Zeah - Wooden Spiral Staircase", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 34502 + ], + "uvType": "BOX", + "uvOrientation": 88, + "uvScale": 0.8 + }, + { + "description": "Old Ones Tent", + "baseMaterial": "FINE_CARPET", + "objectIds": [ + 46324 + ], + "uvType": "BOX", + "uvOrientation": 256 + }, + { + "description": "Hollow Log 1", + "baseMaterial": "BARK", + "objectIds": [ + 1378 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 480 + }, + { + "description": "Old Ones Hole", + "baseMaterial": "DIRT_2", + "objectIds": [ + 46326, 46329, 46332, 46335 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 480 + }, + { + "description": "Objects - Yellow Rocks - Rock - Small", + "baseMaterial": "ROCK_5", + "objectIds": [ + 7116, 7117, 7118 + ], + "uvType": "BOX", + "uvScale": 0.25 + }, + { + "description": "Objects - Yellow Rocks - Rock - Medium", + "baseMaterial": "ROCK_3", + "objectIds": [ + 30787, 30788, 30789 + ], + "uvType": "BOX", + "uvScale": 0.4, + "uvOrientation": 42 + }, + { + "description": "Objects - Yellow Rocks - Rock - Large", + "baseMaterial": "ROCK_4", + "objectIds": [ + 30784, 30785, 30786 + ], + "uvType": "BOX", + "uvScale": 0.33 + }, + { + "description": "Old Ones Dungeon Ruined Walls", + "baseMaterial": "ROCK_3", + "objectIds": [ + 5475, 5477, 5478, 5479, 5480, 5481, 46337 + ], + "uvType": "BOX" + }, + { + "description": "Old Ones Dungeon Walls", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 8931, 8935 + ], + "uvType": "BOX", + "uvOrientation": 100 + }, + { + "description": "Stone- Ruined Steps", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 34354, 34355, 46407 + ], + "uvType": "BOX" + }, + { + "description": "Old Ones Vines", + "baseMaterial": "PLANT_GRUNGE_1", + "objectIds": [ + 41817, 41818, 46381, 46382 + ], + "uvType": "BOX", + "uvScale": 0.6, + "uvOrientation": 42 + }, + { + "description": "Old Ones Stone Pillar", + "baseMaterial": "ROCK_3", + "objectIds": [ + 46415 + ], + "uvType": "BOX", + "uvOrientation": 202 + }, + { + "description": "Old Ones Stone Table", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 46361 + ], + "uvType": "BOX", + "uvOrientation": 202 + }, + { + "description": "Old Ones Stone Table - Big", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 46356 + ], + "uvType": "BOX", + "uvOrientation": 88 + }, + { + "description": "Old Ones Stone Table - Big with objects", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 46357, 46358, 46360, 46371 + ], + "uvType": "BOX", + "uvOrientation": 88 + }, + { + "description": "Old Ones Stone Table - Long", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 46348 + ], + "uvType": "BOX", + "uvOrientation": 202 + }, + { + "description": "Old Ones Stone Table - Long with Objects", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 46349, 46350, 46355, 46365, 46366, 46376, 46377, 46378, 46379 + ], + "uvType": "BOX", + "uvOrientation": 202 + }, + { + "description": "Old Ones Stone Chest", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 46339, 46340, 46341 + ], + "uvType": "BOX", + "uvOrientation": 202 + }, + { + "description": "Old Ones Stone Vase", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 46342, 46343 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 42 + }, + { + "description": "Stone Olmic Pillar", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 34401 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 42 + }, + { + "description": "Molch Ruins Floor", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 34413, 34414, 34415, 34416 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 100 + }, + { + "description": "Olmic Halfwall", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 46425 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 200 + }, + { + "description": "Varlamore - Objects - Stone - Stone block walls", + "baseMaterial": "ROCK_3_SMOOTH", + "objectIds": [ + 42097, 42098, 42099, 42100, 42101, 42102, 52795, 52796 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 147 + }, + { + "description": "Varlamore - Objects - Stone - Stone block steps", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 52345 + ], + "uvType": "BOX", + "uvScale": 0.5, + "uvOrientation": 777 + }, + { + "description": "Varlamore - Objects - Stone - Short column railing", + "baseMaterial": "MARBLE_2_SEMIGLOSS", + "objectIds": [ + 52032 + ], + "uvType": "BOX", + "uvScale": 0.5, + "uvOrientation": 530 + }, + { + "description": "Hunter Guild ceiling lantern", + "objectIds": [ 51731 ], + "flatNormals": true, + "hideVanillaShadows": true + }, + { + "description": "Object - Textured Brick Fireplace", + "baseMaterial": "STONE_NORMALED", + "objectIds": [ + 24969 + ], + "uvType": "BOX", + "uvScale": 0.6, + "uvOrientation": 42 + }, + { + "description": "Objects - Wooden - Hat Rack", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 374 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 84 + }, + { + "description": "Objects - Wooden - Bookcase - Textured - Wide", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 380 + ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientationX": 512, + "flatNormals": true + }, + { + "description": "Objects - Metallic - Range on textured bricks - Textured", + "baseMaterial": "METALLIC_2_SEMIGLOSS", + "objectIds": [ + 9736 + ] + }, + { + "description": "Objects - WOODEN - Textured Torch", + "baseMaterial": "WOOD_GRAIN_2", + "objectIds": [ + 198 + ], + "uvType": "BOX", + "retainVanillaUvs": false, + "uvScale": 0.4 + }, + { + "description": "Decoration - Textured Lumbridge Heraldry", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ + 899, 901 + ], + "uvType": "BOX", + "uvScale": 0.8, + "retainVanillaUvs": false + }, + { + "description": "Wooden Step Ladder", + "objectIds": [ 17390 ], + "baseMaterial": "WOOD_GRAIN_3", + "uvType": "BOX" + }, + { + "description": "Ceramic Big Vase", + "objectIds": [ 5621 ], + "baseMaterial": "GRUNGE_1", + "uvType": "BOX", + "uvScale": 0.4, + "uvOrientation": 462 + }, + { + "description": "Sheep Shearer fence and gate", + "objectIds": [ 12982, 12983, 12986, 12987, 12988, 12989 ], + "baseMaterial": "WOOD_GRAIN_3", + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 512 + }, + { + "description": "Wooden food trough", + "objectIds": [ 301, 3644 ], + "baseMaterial": "WOOD_GRAIN_3", + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientation": 512 + }, + { + "description": "Sheep Shearer Fred the Farmers house walls", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ 12990, 12991 ], + "uvType": "BOX", + "uvScale": 0.8, + "flatNormals": true, + "uvOrientation": 512 + }, + { + "description": "Textured hay carpet", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ 920, 921 ], + "uvType": "BOX", + "uvScale": 0.8, + "retainVanillaUvs": false + }, + { + "description": "Wooden shelves - Empty", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ 12980 ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientationX": 512, + "retainVanillaUvs": false + }, + { + "description": "Wooden hat stand", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ 12981 ], + "uvType": "BOX", + "uvScale": 0.8, + "uvOrientationX": 512, + "retainVanillaUvs": false + }, + { + "description": "Wooden shelf wall mounted", + "baseMaterial": "WOOD_GRAIN_3", + "objectIds": [ 12976 ], + "uvType": "BOX", + "uvScale": 0.5, + "uvOrientationX": 512, + "retainVanillaUvs": false + }, + { + "description": "Perilous Moons entrances & black walls", + "objectIds": [ 42260, 51376, 51461 ], + "baseMaterial": "PURE_BLACK" + }, + { + "description": "Earthbound Cavern rock traps", + "objectIds": [ 51359 ], + "baseMaterial": "ROCK_3", + "uvType": "BOX" + }, + { + "description": "The Blood, Blue & Eclipse Moon statues respectively", + "objectIds": [ 51372, 51373, 51374 ], + "baseMaterial": "ROCK_3_SMOOTH", + "uvType": "BOX" + }, + { + "description": "Disable shadows for fishing spots at Molch", + "npcIds": [ 8523 ], + "castShadows": false + }, + { + "description": "Disable shadows for Colosseum entrance walls", + "objectIds": [ 38847, 50764, 50765, 50766, 50767 ], + "receiveShadows": false, + "flatNormals": true + }, + { + "description": "Disable shadows for Colosseum entrance white backdrop", + "objectIds": [ 50769 ], + "receiveShadows": false, + "flatNormals": true } ] diff --git a/src/main/resources/rs117/hd/scene/textures/000_licenses.txt b/src/main/resources/rs117/hd/scene/textures/000_licenses.txt index 2510302719..fea92a4bb5 100644 --- a/src/main/resources/rs117/hd/scene/textures/000_licenses.txt +++ b/src/main/resources/rs117/hd/scene/textures/000_licenses.txt @@ -38,7 +38,7 @@ WATTLE_1 textures from 3dtextures. All textures on this site are licensed as CC0 METALLIC_1 textures from AmbientCG. All textures on this site are licensed as CC0. -- https://ambientcg.com/view?id=Metal009 -HD_WOOD_PLANKS_1 texture from AmbientCG. All textures on this site are licensed as CC0; Texture is heavily altered; HD_WOOD_PLANKS_1_N generated +HD_WOOD_PLANKS_2 texture from AmbientCG. All textures on this site are licensed as CC0; Texture is heavily altered; HD_WOOD_PLANKS_2_N generated -- https://ambientcg.com/view?id=Planks012 BARK and BARK_N textures from AmbientCG. All textures on this site are licensed as CC0. @@ -84,4 +84,9 @@ ABYSSAL, ABYSSAL_N, and ABYSSAL_D texture from TextureCan. All textures on this -- https://www.texturecan.com/details/137/ ABYSSAL_2, ABYSSAL_2_N, and ABYSSAL_2_D texture from 3d Textures. All textures on this site are licensed as CC0; --- https://3dtextures.me/2019/03/11/abstract-organic-002/ \ No newline at end of file +-- https://3dtextures.me/2019/03/11/abstract-organic-002/ + +HD_WOOD_PLANK_1, HD_WOOD_PLANK_1_N generated by SirFancyBacon using self trained AI; Texture is licensed to the 117HD project and any future forks + +HD_CRATE, HD_CRATE_N texture from 3d Textures. All textures on this site are licensed as CC0; +-- https://3dtextures.me/2016/08/15/wood-planks-004/ \ No newline at end of file diff --git a/src/main/resources/rs117/hd/scene/textures/hd_crate.png b/src/main/resources/rs117/hd/scene/textures/hd_crate.png new file mode 100644 index 0000000000..b44b1113a9 Binary files /dev/null and b/src/main/resources/rs117/hd/scene/textures/hd_crate.png differ diff --git a/src/main/resources/rs117/hd/scene/textures/hd_crate_n.png b/src/main/resources/rs117/hd/scene/textures/hd_crate_n.png new file mode 100644 index 0000000000..09a2784efb Binary files /dev/null and b/src/main/resources/rs117/hd/scene/textures/hd_crate_n.png differ diff --git a/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_1.png b/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_1.png index 47fa5e348e..d0f0e9ef05 100644 Binary files a/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_1.png and b/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_1.png differ diff --git a/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_1_n.png b/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_1_n.png index 48e819db07..4aba8194cf 100644 Binary files a/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_1_n.png and b/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_1_n.png differ diff --git a/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_2.png b/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_2.png new file mode 100644 index 0000000000..47fa5e348e Binary files /dev/null and b/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_2.png differ diff --git a/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_2_n.png b/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_2_n.png new file mode 100644 index 0000000000..48e819db07 Binary files /dev/null and b/src/main/resources/rs117/hd/scene/textures/hd_wood_planks_2_n.png differ diff --git a/src/main/resources/rs117/hd/scene/textures/water_normal_map_1.png b/src/main/resources/rs117/hd/scene/textures/water_normal_map_1.png index 5370bdf8df..3e554a36a9 100644 Binary files a/src/main/resources/rs117/hd/scene/textures/water_normal_map_1.png and b/src/main/resources/rs117/hd/scene/textures/water_normal_map_1.png differ diff --git a/src/main/resources/rs117/hd/scene/textures/water_normal_map_2.png b/src/main/resources/rs117/hd/scene/textures/water_normal_map_2.png index 05402f9348..93c5e68663 100644 Binary files a/src/main/resources/rs117/hd/scene/textures/water_normal_map_2.png and b/src/main/resources/rs117/hd/scene/textures/water_normal_map_2.png differ diff --git a/src/main/resources/rs117/hd/scene/tile_overrides.json b/src/main/resources/rs117/hd/scene/tile_overrides.json index 3159678751..ef0a4b67f0 100644 --- a/src/main/resources/rs117/hd/scene/tile_overrides.json +++ b/src/main/resources/rs117/hd/scene/tile_overrides.json @@ -151,7 +151,7 @@ "overlayIds": [ 85 ], - "groundMaterial": "HD_WOOD_PLANKS_1", + "groundMaterial": "HD_WOOD_PLANKS_2", "replacements": { "WINTER_DIRT": "season == WINTER" } @@ -2798,7 +2798,7 @@ "overlayIds": [ 85 ], - "groundMaterial": "HD_WOOD_PLANKS_1" + "groundMaterial": "HD_WOOD_PLANKS_2" }, { "name": "MORYTANIA_SLAYER_TOWER", @@ -2892,7 +2892,7 @@ "overlayIds": [ 22 ], - "groundMaterial": "HD_WOOD_PLANKS_1" + "groundMaterial": "HD_WOOD_PLANKS_2" }, { "name": "TIRANNWN_PATHS", @@ -3268,6 +3268,49 @@ "WINTER_JAGGED_STONE_TILE_LIGHT_2": "season == WINTER" } }, + { + "name": "FORTIS_MARBLE_FLOOR", + "area": "ZEAH", + "overlayIds": [ 327 ], + "groundMaterial": "MARBLE_2", + "uvScale": 1.1 + }, + { + "name": "FORTIS_WOOD_FLOOR", + "area": "ZEAH_UPPER_LEVELS", + "overlayIds": [ 324 ], + "groundMaterial": "HD_WOOD_PLANKS_2", + "uvScale": 1.1 + }, + { + "name": "FORTIS_RED_DIRT", + "area": "ZEAH", + "overlayIds": [ 324 ], + "groundMaterial": "DIRT", + "uvScale": 1.1 + }, + { + "name": "FORTIS_HOUSE_TILE_FLOOR", + "area": "ZEAH", + "overlayIds": [ 123 ], + "groundMaterial": "TILES_2X2_1_SEMIGLOSS", + "uvScale": 1.1, + "uvOrientation": 256 + }, + { + "name": "FORTIS_CASTLE_WALLS_BRICK_FLOOR", + "area": "ZEAH", + "overlayIds": [ 159 ], + "groundMaterial": "FALADOR_PATHS", + "uvScale": 1.1, + "uvOrientation": 256 + }, + { + "name": "Hunter Guild Water", + "area": "HUNTER_GUILD", + "overlayIds": [ 181 ], + "waterType": "PLAIN_WATER" + }, { "name": "STRANGLEWOOD_HILLSIDE", "area": "THE_STRANGLEWOOD_EXTENDED", @@ -3781,7 +3824,7 @@ "overlayIds": [ 102 ], - "groundMaterial": "HD_WOOD_PLANKS_1" + "groundMaterial": "HD_WOOD_PLANKS_2" }, { "name": "ELID_CAVE_WATER_FIX", @@ -3834,7 +3877,7 @@ "overlayIds": [ 81 ], - "groundMaterial": "HD_WOOD_PLANKS_1" + "groundMaterial": "HD_WOOD_PLANKS_2" }, { "name": "LUNAR_ESSENCE_MINE_WATER", @@ -3877,7 +3920,7 @@ "underlayIds": [ 63 ], - "groundMaterial": "HD_WOOD_PLANKS_1", + "groundMaterial": "HD_WOOD_PLANKS_2", "uvOrientation": 512, "uvScale": 1.333 }, @@ -3887,7 +3930,7 @@ "overlayIds": [ 36 ], - "groundMaterial": "HD_WOOD_PLANKS_1", + "groundMaterial": "HD_WOOD_PLANKS_2", "uvOrientation": 512, "uvScale": 0.75, "shiftLightness": 5 @@ -3952,6 +3995,24 @@ ], "blended": false }, + { + "name": "KELDAGRIM_RED_AXE_CAVES_GRAVEL", + "area": "RED_AXE_REGION", + "overlayIds": [ + 117 + ], + "groundMaterial": "GRAVEL", + "uvScale": 0.6 + }, + { + "name": "KELDAGRIM_RED_AXE_CAVES_GROUND", + "area": "RED_AXE_REGION", + "underlayIds": [ + 111 + ], + "groundMaterial": "ROCKY_CAVE_FLOOR", + "uvScale": 0.6 + }, { "name": "CERBERUS_WATER", "area": "CERBERUS", @@ -3981,6 +4042,23 @@ "SNOW_2": "season == WINTER" } }, + { + "name": "TUTORIAL_ISLAND_TILE", + "area": "TUTORIAL_ISLAND", + "overlayIds": [ + 10 + ], + "groundMaterial": "WORN_TILES" + }, + { + "name": "TUTORIAL_ISLAND_CAVE_FLOOR", + "area": "TUTORIAL_ISLAND_CAVES", + "underlayIds": [ + 63, + 67 + ], + "groundMaterial": "EARTHEN_CAVE_FLOOR" + }, { "name": "VARROCK_PATHS", "area": "OVERWORLD", @@ -4002,6 +4080,218 @@ ], "groundMaterial": "DIRT" }, + { + "name": "VARLAMORE_TEMPLE_CRYPT_CENTER_BLENDING_FIX", + "area": "VARLAMORE_TEMPLE_CRYPT_CENTER", + "overlayIds": [ 11 ], + "blended": false, + "groundMaterial": "DIRT" + }, + { + "name": "VARLAMORE_TEMPLE_CRYPT_BRICK_TILES", + "area": "VARLAMORE_TEMPLE_CRYPT_FAKE_BRICK_TILES", + "overlayIds": [ 139 ], + "groundMaterial": "LASSAR_UNDERCITY_TILES", + "blended": false + }, + { + "name": "VARLAMORE_TEMPLE_CRYPT_SQUARE_TILES", + "area": "VARLAMORE_TEMPLE_CRYPT_FAKE_SQUARE_TILES", + "overlayIds": [ 139 ], + "groundMaterial": "MARBLE_2_SEMIGLOSS", + "blended": false + }, + { + "name": "VARLAMORE_TEMPLE_CRYPT_MARBLE", + "area": "VARLAMORE_TEMPLE_CRYPT", + "overlayIds": [ 228 ], + "groundMaterial": "MARBLE_2_SEMIGLOSS" + }, + { + "name": "VARLAMORE_TEMPLE_CRYPT_WATER", + "area": "VARLAMORE_TEMPLE_CRYPT", + "overlayIds": [ 181, 201 ], + "waterType": "DARK_BLUE_WATER" + }, + { + "name": "COLOSSEUM_TILES", + "area": "COLOSSEUM", + "overlayIds": [ 137, 198, 199 ], + "underlayIds": [ 12, 187, 202 ], + "groundMaterial": "MARBLE_2" + }, + { + "name": "VARLAMORE_SAVANNA_HUNTER_PIT_TRAPS", + "area": "VARLAMORE_SAVANNA_HUNTER_PIT_TRAPS", + "overlayIds": [ 336 ], + "groundMaterial": "GRASS_1", + "blended": false + }, + { + "name": "HUNTER_GUILD_POND", + "area": "HUNTER_GUILD_SURFACE", + "overlayIds": [ 151 ], + "waterType": "WATER_FLAT" + }, + { + "name": "RALOS_RISE_SNOW_PATH", + "area": "VARLAMORE_SNOWY_MOUNTAINS", + "overlayIds": [ 271 ], + "groundMaterial": "SNOW_2" + }, + { + "name": "RALOS_RISE_SNOW", + "area": "VARLAMORE_SNOWY_MOUNTAINS", + "underlayIds": [ 58, 94 ], + "groundMaterial": "SNOW_1", + "shiftLightness": 10, + "replacements": { + "RALOS_RISE_SNOW_DARKER": "l < 55 && s == 0", + "OVERWORLD_GRASS_UNDERLAY": "l < 15 || s > 0" + } + }, + { + "name": "RALOS_RISE_SNOW_DARKER", + "groundMaterial": "SNOW_1", + "shiftLightness": 0 + }, + { + "name": "RALOS_RISE_GRAY_PATH_BLENDING_FIX", + "area": "VARLAMORE_CAM_TORUM_ENTRANCE_PATH_BLENDING_FIX", + "overlayIds": [ 3 ], + "groundMaterial": "DIRT", + "shiftLightness": -8 + }, + { + "name": "RALOS_RISE_GRAY_PATH", + "area": "VARLAMORE_SNOWY_MOUNTAINS", + "overlayIds": [ 3 ], + "groundMaterial": "DIRT", + "blended": false + }, + { + "name": "CAM_TORUM_WATER", + "area": "CAM_TORUM", + "overlayIds": [ 201 ], + "waterType": "DARK_BLUE_WATER" + }, + { + "name": "EARTHBOUND_CAVERN_WATER_DARKER", + "area": "EARTHBOUND_CAVERN", + "overlayIds": [ 201 ], + "waterType": "DARK_BLUE_WATER" + }, + { + "name": "EARTHBOUND_CAVERN_WATER", + "area": "EARTHBOUND_CAVERN", + "overlayIds": [ 158, 181, 201 ], + "waterType": "PLAIN_WATER" + }, + { + "name": "EARTHBOUND_CAVERN_GRASS", + "area": "EARTHBOUND_CAVERN", + "underlayIds": [ 54, 105 ], + "groundMaterial": "GRASS_1" + }, + { + "name": "EARTHBOUND_CAVERN_ROCK", + "area": "EARTHBOUND_CAVERN", + "underlayIds": [ 56, 72 ], + "groundMaterial": "ROCKY_CAVE_FLOOR" + }, + { + "name": "STREAMBOUND_CAVERN_GRASS", + "area": "STREAMBOUND_CAVERN", + "underlayIds": [ 53 ], + "groundMaterial": "GRASS_1" + }, + { + "name": "STREAMBOUND_CAVERN_ROCK", + "area": "STREAMBOUND_CAVERN", + "underlayIds": [ 56, 72 ], + "groundMaterial": "ROCKY_CAVE_FLOOR" + }, + { + "name": "STREAMBOUND_CAVERN_WATER", + "area": "STREAMBOUND_CAVERN", + "overlayIds": [ 156 ], + "waterType": "SWAMP_WATER_FLAT" + }, + { + "name": "ANCIENT_SHRINE_DIRT", + "area": "ANCIENT_SHRINE", + "underlayIds": [ 227 ], + "groundMaterial": "DIRT", + "blended": false + }, + { + "name": "ANCIENT_SHRINE_GRASS", + "area": "ANCIENT_SHRINE", + "underlayIds": [ 36, 81 ], + "groundMaterial": "GRASS_1" + }, + { + "name": "THE_BLUE_MOON_ENTRANCE", + "area": "THE_BLUE_MOON", + "underlayIds": [ 73 ], + "groundMaterial": "MARBLE_2_SEMIGLOSS" + }, + { + "name": "THE_BLUE_MOON_NEAR_WALLS", + "area": "THE_BLUE_MOON", + "overlayIds": [ 333 ], + "groundMaterial": "ICE_1", + "blended": false + }, + { + "name": "THE_BLUE_MOON_FLOOR", + "area": "THE_BLUE_MOON", + "underlayIds": [ 16, 59, 204, 205, 206, 207 ], + "groundMaterial": "SNOW_1" + }, + { + "name": "THE_BLOOD_MOON_ENTRANCE", + "area": "THE_BLOOD_MOON_ENTRANCE", + "underlayIds": [ 118 ], + "groundMaterial": "MARBLE_2_SEMIGLOSS" + }, + { + "name": "THE_BLOOD_MOON_BLOOD", + "area": "THE_BLOOD_MOON", + "overlayIds": [ 331 ], + "waterType": "BLOOD" + }, + { + "name": "THE_BLOOD_MOON_FLOOR", + "area": "THE_BLOOD_MOON", + "underlayIds": [ 116, 118, 142 ], + "groundMaterial": "DIRT" + }, + { + "name": "THE_ECLIPSE_MOON_LAVA", + "area": "THE_ECLIPSE_MOON", + "overlayIds": [ 334 ], + "groundMaterial": "HD_LAVA", + "blended": false + }, + { + "name": "THE_ECLIPSE_MOON_ENTRANCE", + "area": "THE_ECLIPSE_MOON_ENTRANCE", + "underlayIds": [ 212 ], + "groundMaterial": "MARBLE_2_SEMIGLOSS" + }, + { + "name": "THE_ECLIPSE_MOON_FLOOR_BLENDING_FIX", + "area": "THE_ECLIPSE_MOON", + "overlayIds": [ 335 ], + "blended": false + }, + { + "name": "THE_ECLIPSE_MOON_FLOOR", + "area": "THE_ECLIPSE_MOON", + "underlayIds": [ 212, 213 ], + "groundMaterial": "DIRT" + }, { "name": "OVERWORLD_GRASS_OVERLAY", "area": "OVERWORLD", @@ -4786,7 +5076,7 @@ "underlayIds": [ 10 ], - "groundMaterial": "HD_WOOD_PLANKS_1" + "groundMaterial": "HD_WOOD_PLANKS_2" }, { "name": "SLEPE_CHURCH_FLOOR", @@ -4794,7 +5084,7 @@ "underlayIds": [ 94 ], - "groundMaterial": "HD_WOOD_PLANKS_1" + "groundMaterial": "HD_WOOD_PLANKS_2" }, { "name": "SLEPE_HOUSES_WOODEN_FLOOR", @@ -4802,7 +5092,7 @@ "underlayIds": [ 10 ], - "groundMaterial": "HD_WOOD_PLANKS_1" + "groundMaterial": "HD_WOOD_PLANKS_2" }, { "name": "VER_SINHAZA_WATER_FIX", @@ -5206,6 +5496,42 @@ ], "groundMaterial": "EARTHEN_CAVE_FLOOR" }, + { + "name": "GARDEN_OF_DEATH_TILES", + "area": "UNDERGROUND_OLD_ONES_RUINS", + "overlayIds": [ + 133 + ], + "groundMaterial": "MARBLE_2", + "shiftLightness": -4 + }, + { + "name": "GARDEN_OF_DEATH_DIRT", + "area": "UNDERGROUND_OLD_ONES_RUINS", + "underlayIds": [ + 63, 118, 146 + ], + "groundMaterial": "DIRT" + }, + { + "name": "GARDEN_OF_DEATH_GRASSY_DIRT", + "area": "UNDERGROUND_OLD_ONES_RUINS", + "underlayIds": [ + 53, 54 + ], + "groundMaterial": "GRASSY_DIRT" + }, + { + "name": "ZEAH_GRAVEL_PATH", + "area": "ZEAH", + "overlayIds": [ + 99 + ], + "groundMaterial": "VARIED_DIRT", + "replacements": { + "WINTER_DIRT": "season == WINTER" + } + }, { "name": "ZEAH_DIRT_UNDERLAY", "area": "ZEAH", @@ -5806,17 +6132,6 @@ "DEFAULT_DIRT": true } }, - { - "name": "TILE_NEEDS_HUE_DEFINED", - "area": "OVERWORLD", - "underlayIds": [ - 26 - ], - "groundMaterial": "VARIED_DIRT", - "replacements": { - "WINTER_DIRT": "season == WINTER" - } - }, { "name": "DEFENDER_OF_VARROCK_CAVE_FLOOR", "area": "DEFENDER_OF_VARROCK_DUNGEON", @@ -5902,7 +6217,7 @@ }, { "name": "UNDERLAY_SNOW", - "area": "SNOW_REGIONS", + "area": "SNOWY_REGIONS", "underlayIds": [ 16, 55, @@ -5976,6 +6291,7 @@ "area": "OVERWORLD", "underlayIds": [ 13, + 26, 55, 61, 62, diff --git a/src/main/resources/rs117/hd/shadow_geom.glsl b/src/main/resources/rs117/hd/shadow_geom.glsl index 4d146aa4c0..3c0242c448 100644 --- a/src/main/resources/rs117/hd/shadow_geom.glsl +++ b/src/main/resources/rs117/hd/shadow_geom.glsl @@ -71,7 +71,7 @@ void main() { // Scroll UVs fUvw.xy += material.scrollDuration * elapsedTime; // Scale from the center - fUvw.xy = .5 + (fUvw.xy - .5) * material.textureScale; + fUvw.xy = .5 + (fUvw.xy - .5) * material.textureScale.xy; #if SHADOW_TRANSPARENCY fOpacity = gOpacity[i]; diff --git a/src/main/resources/rs117/hd/uniforms/materials.glsl b/src/main/resources/rs117/hd/uniforms/materials.glsl index 23148f10d2..92252385b1 100644 --- a/src/main/resources/rs117/hd/uniforms/materials.glsl +++ b/src/main/resources/rs117/hd/uniforms/materials.glsl @@ -18,8 +18,8 @@ struct Material float flowMapStrength; vec2 flowMapDuration; vec2 scrollDuration; - vec2 textureScale; - vec2 pad; + vec3 textureScale; + float pad; }; layout(std140) uniform MaterialUniforms { diff --git a/src/main/resources/rs117/hd/utils/normals.glsl b/src/main/resources/rs117/hd/utils/normals.glsl index 9983ba41bb..c85c2f2ff9 100644 --- a/src/main/resources/rs117/hd/utils/normals.glsl +++ b/src/main/resources/rs117/hd/utils/normals.glsl @@ -36,6 +36,10 @@ vec3 sampleNormalMap(const Material material, const vec2 uv, const mat3 TBN) { n = linearToSrgb(n); // Scale and shift normal so it can point in both directions n.xy = n.xy * 2 - 1; + // Flip normals when UVs are flipped + n.xy *= sign(material.textureScale.xy); + // Scale the normal map's Z-component to adjust strength + n.z *= material.textureScale.z; // Transform the normal from tangent space to world space n = TBN * n; // Assume the normal is already normalized diff --git a/src/main/resources/rs117/hd/utils/water.glsl b/src/main/resources/rs117/hd/utils/water.glsl index 277b457742..aef6d4968f 100644 --- a/src/main/resources/rs117/hd/utils/water.glsl +++ b/src/main/resources/rs117/hd/utils/water.glsl @@ -150,9 +150,8 @@ vec4 sampleWater(int waterTypeIndex, vec3 viewDir) { vec3 c = waterColorLight; - if (waterReflectionEnabled && distance(waterHeight, IN.position.y) < 32) + if (waterReflectionEnabled && distance(waterHeight, IN.position.y) < 48) c = texture(waterReflectionMap, uv).rgb; - c.rgb = c.rgb *0.9; // Dim water reflections, should be done properly via fresnel surfaceColor = mix(waterColorMid, c, (fresnel - 0.5) * 2); @@ -208,7 +207,7 @@ void sampleUnderwater(inout vec3 outputColor, WaterType waterType, float depth, outputColor = vec3(0); } - if (underwaterCaustics) { + if (shorelineCaustics) { const float scale = 1.75; const float maxCausticsDepth = 128 * 4; @@ -249,6 +248,22 @@ float calculateFresnel(const vec3 I, const vec3 N, const float ior) { void sampleUnderwater(inout vec3 outputColor, WaterType waterType, float depth, float lightDotNormals); vec4 sampleWater(int waterTypeIndex, vec3 viewDir) { + + waterTypeIndex = 13; // DEVELOPMENT OVERRIDE - ALSO SET IN SAMPLEUNDERWATER //TODO look here for water + // 1 = water + // 2 = flat water + // 3 = swamp water + // 4 = swamp water flat + // 5 = poison waste + // 6 = black tar flat + // 7 = blood water + // 8 = ice + // 9 = ice flat + // 10 = muddy water + // 11 = scar sludge + // 12 = abyss bile + // 13 = plain flat water --- #2 is color-matched to model-water in caves etc, while this one isn't + WaterType waterType = getWaterType(waterTypeIndex); // vec2 baseUv = vUv[0].xy * IN.texBlend.x + vUv[1].xy * IN.texBlend.y + vUv[2].xy * IN.texBlend.z; @@ -346,9 +361,19 @@ vec4 sampleWater(int waterTypeIndex, vec3 viewDir) { // // underglow // vec3 underglowOut = underglowColor * max(normals.y, 0) * underglowStrength; - const float speed = .25; - vec2 uv1 = worldUvs(11) + animationFrame(sqrt(11.) / speed * waterType.duration); - vec2 uv2 = worldUvs(3) + animationFrame(sqrt(3.) / speed * waterType.duration); + + float speed = .024; + if(waterTypeIndex == 8 || waterTypeIndex == 9) // ice + { + speed = 0.00000001; // 0 speed bugs out the normals code and prevents rendering, glacier speed is fine + } + else + speed = 0.024; + float waveSizeConfig = waterWaveSizeConfig / 100.f; + float waveSpeedConfig = waterWaveSpeedConfig / 100.f; + speed *= waveSpeedConfig; + vec2 uv1 = worldUvs(26) - animationFrame(sqrt(11.) / speed * waterType.duration / vec2(-1, 4)); + vec2 uv2 = worldUvs(6) - animationFrame(sqrt(3.) / speed * waterType.duration * 1.5 /vec2(2, -1)); // get diffuse textures vec3 n1 = linearToSrgb(texture(textureArray, vec3(uv1, MAT_WATER_NORMAL_MAP_1.colorMap)).xyz); @@ -362,20 +387,33 @@ vec4 sampleWater(int waterTypeIndex, vec3 viewDir) { n2.z *= -1; n1.xyz = n1.xzy; n2.xyz = n2.xzy; - // UDN blending - vec3 normals = normalize(vec3(n1.xy + n2.xy, n1.z + n2.z)); -// vec3 normals = normalize(vec3(n1.xy + n2.xy, n1.z)); -// return vec4(n1, 1); -// return vec4(n2, 1); -// return vec4(normals, 1); + n1.y /= 0.225; // scale normals + if(waterTypeIndex == 6 || waterTypeIndex == 8 || waterTypeIndex == 9 || waterTypeIndex == 12) // black tar, ice, ice flat, abyss bile + { + n1.y /= 0.3; + } + n1.y /= waveSizeConfig; + n1 = normalize(n1); + n2.y /= 0.8; // scale normals + if(waterTypeIndex == 6 || waterTypeIndex == 8 || waterTypeIndex == 9 || waterTypeIndex == 12) // black tar, ice, ice flat, abyss bile + { + n2.y /= 0.3; + } + n2.y /= waveSizeConfig; + n2 = normalize(n2); + vec3 normals = normalize(n1+n2); + normals = normalize(vec3(n1.xy + n2.xy, n1.z + n2.z)); + vec3 normalScatter = normals; vec3 fragToCam = viewDir; - // fresnel reflection + // fresnel for fake sky reflection and real planar reflection float fresnel = calculateFresnel(normals, fragToCam, 1.333); vec3 c = srgbToLinear(fogColor); - if (waterReflectionEnabled) { // TODO: compare with waterHeight instead && IN.position.y > -128) { + vec4 d = vec4(0); + + if (waterReflectionEnabled && abs(IN.position.y - waterHeight) < 32) { //only render reflection on water within a quarter-tile height of correct for the reflection texture vec3 I = -viewDir; // incident vec3 N = normals; // normal vec3 R = reflect(I, N); @@ -402,15 +440,14 @@ vec4 sampleWater(int waterTypeIndex, vec3 viewDir) { int waterDepth = vTerrainData[0] >> 8 & 0x7FF; vec2 distortion = vec2(x, y) * 50 * distortionFactor; // TODO: Don't distort too close to the shore - float shoreLineMask = 1 - dot(IN.texBlend, vec3(vColor[0].x, vColor[1].x, vColor[2].x)); -// distortion *= 1 - pow(shoreLineMask, 1 / 5.); + float shoreLineMask = 1.0 - dot(IN.texBlend, vec3(vColor[0].x, vColor[1].x, vColor[2].x)); + distortion *= 1.4 - (shoreLineMask *1.54); // safety factor to remove artifacts uv += texelSize * distortion; uv = clamp(uv, texelSize, 1 - texelSize); // This will be linear or sRGB depending on the linear alpha blending setting c = texture(waterReflectionMap, uv, -1).rgb; -// c = textureBicubic(waterReflectionMap, uv).rgb; #if !LINEAR_ALPHA_BLENDING // When linear alpha blending is on, the texture is in sRGB, and OpenGL will automatically convert it to linear c = srgbToLinear(c); @@ -419,93 +456,446 @@ vec4 sampleWater(int waterTypeIndex, vec3 viewDir) { // ALWAYS RETURN IN sRGB FROM THIS FUNCTION (been burned by this a couple times) -// return vec4(linearToSrgb(c * fresnel), 1); // correct, but disables alpha blending -// return vec4(linearToSrgb(c), fresnel); // correct, but bad banding due to alpha precision - float alpha = fresnel; + vec3 foam = vec3(0); + + #include WATER_FOAM + #if WATER_FOAM + vec2 flowMapUv = worldUvs(15) + animationFrame(50 * waterType.duration); + float flowMapStrength = 0.025; + vec2 uvFlow = texture(textureArray, vec3(flowMapUv, waterType.flowMap)).xy; + vec2 uv3 = vUv[0].xy * IN.texBlend.x + vUv[1].xy * IN.texBlend.y + vUv[2].xy * IN.texBlend.z + uvFlow * flowMapStrength; + float foamMask = texture(textureArray, vec3(uv3, waterType.foamMap)).r; + float foamAmount = 1 - dot(IN.texBlend, vec3(vColor[0].x, vColor[1].x, vColor[2].x)); + float foamDistance = 1; + vec3 foamColor = waterType.foamColor; + if(waterTypeIndex == 13) + { + foamColor = vec3(0.5); + } + foamColor = srgbToLinear(foamColor) * foamMask * (ambientColor * ambientStrength + lightColor * lightStrength); + foamAmount = clamp(pow(1.0 - ((1.0 - foamAmount) / foamDistance), 3), 0.0, 1.0) * waterType.hasFoam; + foamAmount *= 0.08; + foam.rgb = foamColor * foamAmount * (1 - foamAmount) * (waterFoamAmountConfig /100.f); + alpha = foamAmount + alpha * (1 - foamAmount); + + if(waterTypeIndex == 3 || waterTypeIndex == 4) // swamp water + swamp water flat + { + foam.rgb *= vec3(1.3, 1.3, 0.4); + } - if (waterTypeIndex == 7) { - vec3 waterColor = srgbToLinear(vec3(102, 0, 0) / 255.f) * 1; - if (dot(c, c) == 0) { -// c = vec3(167, 66, 66) / 255; -// c = vec3(100, 100, 100) / 255; -// c = srgbToLinear(vec3(25, 0, 0) / 255.f) * 10; - c = srgbToLinear(vec3(100, 0, 0) / 255.f); + if(waterTypeIndex == 5) // toxic waste + { + foam.rgb *= vec3(0.7, 0.7, 0.7); } - } - if (waterType.isFlat) { - vec3 underwaterSrgb = packedHslToSrgb(6676); - int depth = 50; + if(waterTypeIndex == 6) // black tar + { + foam.rgb *= vec3(1.0, 1.0, 1.0); + } + + if(waterTypeIndex == 7) // blood + { + foam.rgb *= vec3(1.6, 0.7, 0.7); + } + + if(waterTypeIndex == 8) // ice + { + foam.rgb *= vec3(0.5, 0.5, 0.5); + } + + if(waterTypeIndex == 9) // ice flat + { + foam.rgb *= vec3(0.5, 0.5, 0.5); + } + + if(waterTypeIndex == 10) // muddy water + { + foam.rgb *= vec3(1.0, 0.5, 0.5); + } + + if(waterTypeIndex == 11) // scar sludge + { + foam.rgb *= vec3(0.9, 1.2, 0.9); + } + + if(waterTypeIndex == 12) // abyss bile + { + foam.rgb *= vec3(1.0, 0.7, 0.3); + } + + if(waterTypeIndex == 13) // plain water flat + { + foam.rgb *= vec3(1); + } + + #endif + + #include WATER_LIGHT_SCATTERING + #if WATER_LIGHT_SCATTERING + if(waterTypeIndex == 1 || waterTypeIndex == 2 || waterTypeIndex == 3 || waterTypeIndex == 4 || waterTypeIndex == 5 || waterTypeIndex == 13) + { + + vec3 waterTypeExtinction = vec3(1); + + if(waterTypeIndex == 1 || waterTypeIndex == 2 || waterTypeIndex == 13) + { + waterTypeExtinction = vec3(1); + } + + if(waterTypeIndex == 3 || waterTypeIndex == 4 || waterTypeIndex == 5) + { + waterTypeExtinction = vec3(2, 2, 2); // Light absorption for swamp water and toxic waste + } + + if(waterTypeIndex == 7) + { + waterTypeExtinction = vec3(0.6, 30, 30); // Light absorption for blood + } + + if(waterTypeIndex == 10) + { + waterTypeExtinction = vec3(2, 2, 5); // Light absorption for muddy water + } + + if(waterTypeIndex == 11) + { + waterTypeExtinction = vec3(2, 2, 2); // Light absorption for scar sludge + } + if(waterTypeIndex == 12) + { + waterTypeExtinction = vec3(1, 1, 1); // Light absorption for abyss bile + } + + float scatterStrength = 3; + vec3 scatterExtinction = vec3(0); + float scatterDepth = 128 * 2 * 1; + scatterExtinction.r = exp(-scatterDepth * 0.003090 * waterTypeExtinction.r); + scatterExtinction.g = exp(-scatterDepth * 0.001981 * waterTypeExtinction.g); + scatterExtinction.b = exp(-scatterDepth * 0.001548 * waterTypeExtinction.b); + + //return vec4(scatterExtinction, 1); + + float waveStrength = -normalScatter.y; + waveStrength = 1 - waveStrength; + waveStrength = pow(waveStrength, 1 / 1.4f); + waveStrength *=0.3; + //return vec4(vec3(waveStrength), 1); + + d.r = (scatterStrength * scatterExtinction.r * waveStrength); + d.g = (scatterStrength * scatterExtinction.g * waveStrength); + d.b = (scatterStrength * scatterExtinction.b * waveStrength); + } + #endif + + vec4 reflection = vec4(c, fresnel); + vec4 scattering = vec4(d.rgb, 0.5); + + vec4 dst = vec4(0); + + if (waterTransparencyType == 1 || waterTypeIndex == 2 || waterTypeIndex == 4 || waterTypeIndex == 6 || waterTypeIndex == 9 || waterTypeIndex == 13) // Opaque setting or flat water + { + if(waterTypeIndex == 2) // Flat cave water + { + vec3 caveWaterFlatSrgb = vec3(0.22, 0.51, 0.6); + c = 0 + (srgbToLinear(caveWaterFlatSrgb) * (1 - alpha)); + alpha = 1; + dst = vec4(c.rgb, alpha); + reflection.rgb *= 1; + dst = reflection * reflection.a + dst * (1 - reflection.a); // blend in reflection + dst += vec4(foam, 0); // add foam on top + dst.rgb = linearToSrgb(dst.rgb); + return vec4(dst.rgb, 1); // cave water flat + } + + if(waterTypeIndex == 3 || waterTypeIndex == 4) // Swamp Water Flat + { + vec3 swampFlatSrgb = vec3(0.275, 0.275, 0.1375); + c = 0 + (srgbToLinear(swampFlatSrgb) * (1 - alpha)); + alpha = 1; + dst = vec4(c.rgb, alpha); + reflection.rgb *= 0.25; + dst = reflection * reflection.a + dst * (1 - reflection.a); // blend in reflection + dst += vec4(foam, 0); // add foam on top + dst.rgb = linearToSrgb(dst.rgb); + return vec4(dst.rgb, 1); // swamp water flat + } + + if(waterTypeIndex == 5) // toxic waste flat + { + vec3 wasteFlatSrgb = vec3(0.2, 0.2, 0.2); + c = 0 + (srgbToLinear(wasteFlatSrgb) * (1 - alpha)); + alpha = 1; + dst = vec4(c.rgb, alpha); + reflection.rgb *= 0.3; + dst = reflection * reflection.a + dst * (1 - reflection.a); // blend in reflection + dst += vec4(foam, 0); // add foam on top + dst.rgb = linearToSrgb(dst.rgb); + return vec4(dst.rgb, 1); // black tar flat + } + + if(waterTypeIndex == 6) // black tar flat + { + vec3 tarFlatSrgb = vec3(0, 0, 0); + c = 0 + (srgbToLinear(tarFlatSrgb) * (1 - alpha)); + alpha = 1; + dst = vec4(c.rgb, alpha); + reflection.rgb *= 0.3; + dst = reflection * reflection.a + dst * (1 - reflection.a); // blend in reflection + dst += vec4(foam, 0); // add foam on top + dst.rgb = linearToSrgb(dst.rgb); + return vec4(dst.rgb, 1); // black tar flat + } + + if(waterTypeIndex == 7) // Blood flat + { + vec3 bloodSrgb = vec3(0.3, 0, 0); + c = 0 + (srgbToLinear(bloodSrgb) * (1 - alpha)); + alpha = 1; + dst = vec4(c.rgb, alpha); + reflection.rgb *= 0.75; + dst = reflection * reflection.a + dst * (1 - reflection.a); // blend in reflection + dst += vec4(foam, 0); // add foam on top + dst.rgb = linearToSrgb(dst.rgb); + return vec4(dst.rgb, 1); // blood flat water + } + + if(waterTypeIndex == 8 || waterTypeIndex == 9) // Ice flat + { + vec3 iceFlatSrgb = vec3(0.25, 0.25, 0.28); + c = 0 + (srgbToLinear(iceFlatSrgb) * (1 - alpha)); + alpha = 1; + dst = vec4(c.rgb, alpha); + reflection.rgb *= 0.8; + dst = reflection * reflection.a + dst * (1 - reflection.a); // blend in reflection + dst += vec4(foam, 0); // add foam on top + dst.rgb = linearToSrgb(dst.rgb); + return vec4(dst.rgb, 1); // flat ice + } + + if(waterTypeIndex == 10) // Muddy water flat + { + vec3 mudFlatSrgb = vec3(0.28, 0.18, 0); + c = 0 + (srgbToLinear(mudFlatSrgb) * (1 - alpha)); + alpha = 1; + dst = vec4(c.rgb, alpha); + reflection.rgb *= 0.4; + dst = reflection * reflection.a + dst * (1 - reflection.a); // blend in reflection + dst += vec4(foam, 0); // add foam on top + dst.rgb = linearToSrgb(dst.rgb); + return vec4(dst.rgb, 1); // flat mud + } + + if(waterTypeIndex == 11) // Scar Sludge flat + { + vec3 sludgeFlatSrgb = vec3(0.45, 0.49, 0.43); + c = 0 + (srgbToLinear(sludgeFlatSrgb) * (1 - alpha)); + alpha = 1; + dst = vec4(c.rgb, alpha); + reflection.rgb *= 0.9; + dst = reflection * reflection.a + dst * (1 - reflection.a); // blend in reflection + dst += vec4(foam, 0); // add foam on top + dst.rgb = linearToSrgb(dst.rgb); + return vec4(dst.rgb, 1); // flat scar sludge + } + + if(waterTypeIndex == 12) // abyss bile flat + { + vec3 abyssBileFlatSrgb = vec3(0.52, 0.43, 0.18); + c = 0 + (srgbToLinear(abyssBileFlatSrgb) * (1 - alpha)); + alpha = 1; + dst = vec4(c.rgb, alpha); + reflection.rgb *= 0.9; + dst = reflection * reflection.a + dst * (1 - reflection.a); // blend in reflection + dst += vec4(foam, 0); // add foam on top + dst.rgb = linearToSrgb(dst.rgb); + return vec4(dst.rgb, 1); // flat abyss bile + } + + if(waterTypeIndex == 13 || waterTypeIndex == 1) // plain water flat + { + vec3 plainWaterFlatSrgb = vec3(0.05, 0.1, 0.1); + c = 0 + (srgbToLinear(plainWaterFlatSrgb) * (1 - alpha)); + alpha = 1; + dst = vec4(c.rgb, alpha); + reflection.rgb *= 1; + dst = reflection * reflection.a + dst * (1 - reflection.a); // blend in reflection + dst += vec4(foam, 0); // add foam on top + dst.rgb = linearToSrgb(dst.rgb); + return vec4(dst.rgb, 1); // flat plain water + } + + float flatWaterTileDepth = 3; + float depth = 128 * 2 * flatWaterTileDepth; // tile depth, *2 for round trip, * for number of tiles + vec3 underwaterExtinction = vec3(0); + underwaterExtinction.r = exp(-depth * 0.003090); + underwaterExtinction.g = exp(-depth * 0.001981); + underwaterExtinction.b = exp(-depth * 0.001548); + + vec3 underwaterLinear = vec3(lightStrength) * 0.1 * underwaterExtinction; + vec3 underwaterSrgb = linearToSrgb(underwaterLinear); + sampleUnderwater(underwaterSrgb, waterType, depth, dot(lightDir, normals)); c = c * alpha + srgbToLinear(underwaterSrgb) * (1 - alpha); alpha = 1; + + dst = scattering * scattering.a + vec4(c.rgb, alpha) * (1 - scattering.a); // blend in scattering + dst = reflection * reflection.a + dst * (1 - reflection.a); // blend in reflection + dst += vec4(foam, 0); // add foam on top + + dst.rgb = linearToSrgb(dst.rgb); + return vec4(dst.rgb, 1); // flat water } - // Like before, sampleWater needs to return sRGB - c = linearToSrgb(c); - return vec4(c.rgb, alpha); + if(waterTypeIndex == 3) // swamp water + { + reflection.rgb *= 0.3; // dim reflection + dst.rgb += vec3(0.1, 0.1, 0.05); // inject color + + } + + if(waterTypeIndex == 5) // toxic waste + { + reflection.rgb *= 0.3; // dim reflection + dst.rgb += vec3(0.05, 0.05, 0.05); // inject color + + } + + if(waterTypeIndex == 7) // blood + { + reflection.rgb *= 0.75; // dim reflection + dst.rgb += vec3(0.16, 0, 0); // inject color + + } + + if(waterTypeIndex == 8) // ice + { + reflection.rgb *= 0.8; // dim reflection + dst.rgb += vec3(0.07, 0.07, 0.1); // inject color + } + + if(waterTypeIndex == 10) // muddy water + { + reflection.rgb *= 0.4; // dim reflection + dst.rgb += vec3(0.13, 0.06, 0); // inject color + } + + if(waterTypeIndex == 11) // scar sludge + { + reflection.rgb *= 0.9; + dst.rgb += vec3(0.3, 0.37, 0.3); // inject color + } + + if(waterTypeIndex == 12) // abyss bile + { + reflection.rgb *= 0.9; + dst.rgb += vec3(0.42, 0.29, 0.075); // inject color + } + + + dst = scattering * scattering.a + dst * (1 - scattering.a); // blend in scattering + dst = reflection * reflection.a + dst * (1 - reflection.a); // blend in reflection + foam.rgb *= 1.5; // foam otherwise looks disproportionately weak on transparent water due to reduced alpha + + dst += vec4(foam, 0); // add foam on top + + dst.rgb /= (dst.a); + dst.rgb = linearToSrgb(dst.rgb); + return dst; // transparent water } void sampleUnderwater(inout vec3 outputColor, WaterType waterType, float depth, float lightDotNormals) { // underwater terrain - float lowestColorLevel = 500; - float midColorLevel = 150; - float surfaceLevel = IN.position.y - depth; // e.g. -1600 + outputColor = srgbToLinear(outputColor); + outputColor.r *=0.7; // dirt texture looks unnaturally dry/bright/red in shallow water, remove some before further blending -// outputColor = vec3(0); return; + vec3 camToFrag = normalize(IN.position - cameraPos); + float distanceToSurface = depth / camToFrag.y; + float totalDistance = depth + distanceToSurface; + int waterTypeIndex = vTerrainData[0] >> 3 & 0x1F; - if (underwaterCaustics) { - const float scale = 1.75; - const float maxCausticsDepth = 128 * 4; + //TODO water types are here + waterTypeIndex = 13; // DEVELOPMENT OVERRIDE - ALSO SET IN SAMPLEWATER + // 1 = water + // 2 = flat water + // 3 = swamp water + // 4 = swamp water flat + // 5 = poison waste + // 6 = black tar flat + // 7 = blood water + // 8 = ice + // 9 = ice flat + // 10 = muddy water + // 11 = scar sludge + // 12 = abyss bile + // 13 = plain flat water --- #2 is color-matched to model-water in caves etc, while this one isn't + + float lightPenetration = 0.5 + (waterTransparencyConfig / 44.444); // Scale from a range of 0% = 0.5, 100% = 2.75, 130% = 3.425 + + // Exponential falloff of light intensity when penetrating water, different for each color + vec3 extinctionColors = vec3(0); + vec3 waterTypeExtinction = vec3(0); + + if(waterTypeIndex == 1 || waterTypeIndex == 2 || waterTypeIndex == 8 || waterTypeIndex == 13) + { + waterTypeExtinction = vec3(1); + } - vec2 causticsUv = worldUvs(scale); + if(waterTypeIndex == 3 || waterTypeIndex == 4) + { + waterTypeExtinction = vec3(2, 2, 2); // Light absorption for swamp water + } - float depthMultiplier = (IN.position.y - surfaceLevel - maxCausticsDepth) / -maxCausticsDepth; - depthMultiplier *= depthMultiplier; + if(waterTypeIndex == 5) + { + waterTypeExtinction = vec3(3, 3, 3); // Light absorption for toxic waste + } - causticsUv *= .75; + if(waterTypeIndex == 7) + { + waterTypeExtinction = vec3(0.6, 30, 30); // Light absorption for blood + } - const ivec2 direction = ivec2(1, -2); - vec2 flow1 = causticsUv + animationFrame(17) * direction; - vec2 flow2 = causticsUv * 1.5 + animationFrame(23) * -direction; - vec3 caustics = sampleCaustics(flow1, flow2, .005); + if(waterTypeIndex == 10) + { + waterTypeExtinction = vec3(1.5, 3, 6); // Light absorption for muddy water + } - vec3 causticsColor = underwaterCausticsColor * underwaterCausticsStrength; - outputColor.rgb *= 1 + caustics * causticsColor * depthMultiplier * lightDotNormals * lightStrength; + if(waterTypeIndex == 11) + { + waterTypeExtinction = vec3(0.75, 1.125, 1.5); // Light absorption for scar sludge } - outputColor = srgbToLinear(outputColor); + if(waterTypeIndex == 12) + { + waterTypeExtinction = vec3(1, 1, 1); // Light absorption for abyss bile + } - // Since we've already applied refraction displacement, we can simply calculate the distance - // surface to camera - vec3 v = normalize(cameraPos - IN.position); - vec3 n = vec3(0, -1, 0); // assume level surface - float distance = depth / dot(v, n); - -// outputColor = vec3(0); return; - -// vec3 depthColor2 = srgbToLinear(waterType.depthColor) * .14 * vec3(.4, .275, .09); -// vec3 depthColor2 = srgbToLinear(vec3(6.3, 16, 29.4) / 255.f) * .1; -// vec3 depthColor1 = srgbToLinear(vec3(25.5, 73.5, 100) / 255.f); - vec3 depthColor1 = vec3(0); - vec3 depthColor2 = srgbToLinear(vec3(6, 96.5, 50.5) / 255.f) * 1; -// vec3 depthColor2 = srgbToLinear(vec3(25.5, 73.5, 100) / 255.f) * 1; - float extinction = exp(-distance * .008); -// outputColor = mix(depthColor1, outputColor, extinction); -// outputColor = mix(depthColor2, outputColor, extinction); - outputColor = mix(mix(depthColor1, depthColor2, extinction), outputColor, extinction); + extinctionColors.r = exp(-totalDistance * (0.003090 / lightPenetration) * waterTypeExtinction.r); + extinctionColors.g = exp(-totalDistance * (0.001981 / lightPenetration) * waterTypeExtinction.g); + extinctionColors.b = exp(-totalDistance * (0.001548 / lightPenetration) * waterTypeExtinction.b); - int waterTypeIndex = vTerrainData[0] >> 3 & 0x1F; - if (waterTypeIndex == 7) { - vec3 waterColor = srgbToLinear(vec3(25, 0, 0) / 255.f); - float extinction = exp(-distance * 1); - extinction = 0; - outputColor = mix(waterColor, outputColor, extinction); + if (underwaterCaustics && (waterTransparencyType ==0 || depth <=500)) { + const float scale = 2.5; + vec2 causticsUv = worldUvs(scale); + causticsUv *= 0.75; + const ivec2 direction = ivec2(1, -2); + vec2 flow1 = causticsUv + animationFrame(17) * direction; + vec2 flow2 = causticsUv * 1.5 + animationFrame(23) * -direction; + vec3 caustics = sampleCaustics(flow1, flow2, .005); + vec3 causticsColor = underwaterCausticsColor * underwaterCausticsStrength; + if(waterTransparencyType ==1 && depth <=500) // reduce caustics brightness for shallow opaque water + { + causticsColor *= 0.5; + } + if(waterTypeIndex == 8 || waterTypeIndex == 9) // ice + { + causticsColor *= 0; + } + outputColor *= 1 + caustics * causticsColor * extinctionColors * lightDotNormals * lightStrength * (waterCausticsStrengthConfig / 100.f); } - outputColor = vec3(0); return; - + outputColor = mix(vec3(0), outputColor, extinctionColors); outputColor = linearToSrgb(outputColor); } -#endif +#endif \ No newline at end of file diff --git a/src/test/java/rs117/hd/test/LightConfigTest.java b/src/test/java/rs117/hd/test/LightConfigTest.java deleted file mode 100644 index 2e95e794a0..0000000000 --- a/src/test/java/rs117/hd/test/LightConfigTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package rs117.hd.test; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import rs117.hd.scene.LightManager; -import rs117.hd.scene.lights.LightDefinition; - -import static org.junit.Assert.assertEquals; -import static rs117.hd.utils.ResourcePath.path; - -public class LightConfigTest { - private final ByteArrayOutputStream stderr = new ByteArrayOutputStream(); - private final ByteArrayOutputStream stdout = new ByteArrayOutputStream(); - private final PrintStream originalStdout = System.out; - private final PrintStream originalStderr = System.err; - - @Before - public void setupStreams() { - System.setOut(new PrintStream(stdout)); - System.setErr(new PrintStream(stderr)); - } - - @After - public void restoreStreams() { - System.setErr(originalStdout); - System.setErr(originalStderr); - } - - @Test - public void testLoad() { - Gson gson = new GsonBuilder().setLenient().create(); - LightManager lightManager = new LightManager(); - lightManager.loadConfig(gson, path(LightConfigTest.class, "lights.json"), true); - - // can we get the same light for both of its raw IDs? - LightDefinition spitRoastLight = lightManager.OBJECT_LIGHTS.get(5608).get(0); - assertEquals(spitRoastLight, lightManager.OBJECT_LIGHTS.get(4267).get(0)); - - // is its data correct? - assertEquals("SPIT_ROAST", spitRoastLight.description); - assertEquals(50, spitRoastLight.height); - assertEquals("CENTER", spitRoastLight.alignment.toString()); - assertEquals(250, spitRoastLight.radius); - assertEquals(12.5, spitRoastLight.strength, 0.0); - assertEquals("FLICKER", spitRoastLight.type.toString()); - assertEquals(0.0, spitRoastLight.duration, 0.0); - assertEquals(20.0, spitRoastLight.range, 0.0); - assertEquals(0.9743002, spitRoastLight.color[0], 0.001); - assertEquals(0.29613828659057617, spitRoastLight.color[1], 0.001); - assertEquals(5.6921755E-5, spitRoastLight.color[2], 0.001); - } -} diff --git a/src/test/java/rs117/hd/test/utils/ModelHashTest.java b/src/test/java/rs117/hd/test/utils/ModelHashTest.java deleted file mode 100644 index 21e3527e4b..0000000000 --- a/src/test/java/rs117/hd/test/utils/ModelHashTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package rs117.hd.test.utils; - -import net.runelite.api.*; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import rs117.hd.utils.ModelHash; - -import static org.junit.Assert.assertEquals; - -@RunWith(MockitoJUnitRunner.class) -public class ModelHashTest { - @Mock - private Client client; - - @Test - public void testModelHashPackingAndParsing() { - int id = 44630; - long uuid = 0x15cac8000L; - long hash = 0x15cac9ab7L; - assertEquals(id, ModelHash.getIdOrIndex(uuid)); - assertEquals(uuid, ModelHash.packUuid(ModelHash.TYPE_OBJECT, id)); - assertEquals(id, ModelHash.getIdOrIndex(uuid)); - assertEquals(ModelHash.TYPE_OBJECT, ModelHash.getType(uuid)); - assertEquals(uuid, ModelHash.generateUuid(client, hash, null)); - assertEquals(id, ModelHash.getIdOrIndex(ModelHash.generateUuid(client, hash, null))); - } -} diff --git a/src/test/java/rs117/hd/test/utils/ExportAreasForExplv.java b/src/test/java/rs117/hd/tools/ExportAreasForExplv.java similarity index 95% rename from src/test/java/rs117/hd/test/utils/ExportAreasForExplv.java rename to src/test/java/rs117/hd/tools/ExportAreasForExplv.java index 2a5ccb60ba..58d0f9c6e6 100644 --- a/src/test/java/rs117/hd/test/utils/ExportAreasForExplv.java +++ b/src/test/java/rs117/hd/tools/ExportAreasForExplv.java @@ -1,4 +1,4 @@ -package rs117.hd.test.utils; +package rs117.hd.tools; import java.util.Arrays; import java.util.HashSet; diff --git a/src/test/java/rs117/hd/test/utils/IdentifyUnusedTextures.java b/src/test/java/rs117/hd/tools/IdentifyUnusedTextures.java similarity index 96% rename from src/test/java/rs117/hd/test/utils/IdentifyUnusedTextures.java rename to src/test/java/rs117/hd/tools/IdentifyUnusedTextures.java index 5caa0dd9bc..def055566d 100644 --- a/src/test/java/rs117/hd/test/utils/IdentifyUnusedTextures.java +++ b/src/test/java/rs117/hd/tools/IdentifyUnusedTextures.java @@ -1,4 +1,4 @@ -package rs117.hd.test.utils; +package rs117.hd.tools; import java.util.Arrays; import java.util.stream.Collectors; diff --git a/src/test/java/rs117/hd/test/ModelHasherTest.java b/src/test/java/rs117/hd/tools/ModelHasherPerformanceTest.java similarity index 97% rename from src/test/java/rs117/hd/test/ModelHasherTest.java rename to src/test/java/rs117/hd/tools/ModelHasherPerformanceTest.java index 4dfa77d91e..a19f6266a2 100644 --- a/src/test/java/rs117/hd/test/ModelHasherTest.java +++ b/src/test/java/rs117/hd/tools/ModelHasherPerformanceTest.java @@ -1,4 +1,4 @@ -package rs117.hd.test; +package rs117.hd.tools; import java.util.ArrayList; import java.util.Arrays; @@ -6,7 +6,7 @@ import junit.framework.TestCase; import rs117.hd.model.ModelHasher; -public class ModelHasherTest extends TestCase { +public class ModelHasherPerformanceTest extends TestCase { private final ArrayList intArrays; private final Random random; private final int testDataCount; @@ -14,7 +14,7 @@ public class ModelHasherTest extends TestCase { private long accumulatedHash = 0; - public ModelHasherTest() { + public ModelHasherPerformanceTest() { this.intArrays = new ArrayList<>(); this.random = new Random(1337); this.testDataCount = 1000; diff --git a/src/test/java/rs117/hd/test/utils/AABBTest.java b/src/test/java/rs117/hd/utils/AABBTest.java similarity index 89% rename from src/test/java/rs117/hd/test/utils/AABBTest.java rename to src/test/java/rs117/hd/utils/AABBTest.java index 66d2957651..f10d7abd83 100644 --- a/src/test/java/rs117/hd/test/utils/AABBTest.java +++ b/src/test/java/rs117/hd/utils/AABBTest.java @@ -1,8 +1,7 @@ -package rs117.hd.test.utils; +package rs117.hd.utils; import org.junit.Assert; import org.junit.Test; -import rs117.hd.utils.AABB; public class AABBTest { @Test diff --git a/src/test/java/rs117/hd/test/utils/ColorUtilsTest.java b/src/test/java/rs117/hd/utils/ColorUtilsTest.java similarity index 61% rename from src/test/java/rs117/hd/test/utils/ColorUtilsTest.java rename to src/test/java/rs117/hd/utils/ColorUtilsTest.java index fa1532a8a2..421bb58f26 100644 --- a/src/test/java/rs117/hd/test/utils/ColorUtilsTest.java +++ b/src/test/java/rs117/hd/utils/ColorUtilsTest.java @@ -1,35 +1,40 @@ -package rs117.hd.test.utils; +package rs117.hd.utils; import java.util.Arrays; import org.junit.Test; -import rs117.hd.utils.ColorUtils; import static junit.framework.TestCase.assertEquals; +import static rs117.hd.utils.ColorUtils.linearToSrgb; +import static rs117.hd.utils.ColorUtils.packHsl; +import static rs117.hd.utils.ColorUtils.srgbToLinear; +import static rs117.hd.utils.ColorUtils.srgbToPackedHsl; +import static rs117.hd.utils.ColorUtils.unpackHsl; public class ColorUtilsTest { @Test public void testJagexHslPacking() { + float[] hsl; for (int counter = 0; (counter & ~0xFFFF) == 0; counter++) { int packedHslBefore = counter; - float[] hsl = ColorUtils.unpackHsl(packedHslBefore); + hsl = unpackHsl(packedHslBefore); // Zero saturation or min/max lightness yield the same color if (hsl[1] <= .0625f || hsl[2] == 0 || hsl[2] >= 127f / 128) { hsl[0] = .0078125f; hsl[1] = .0625f; - packedHslBefore = ColorUtils.packHsl(hsl); + packedHslBefore = packHsl(hsl); } float[] srgbBefore = ColorUtils.packedHslToSrgb(packedHslBefore); - float[] srgbAfter = ColorUtils.linearToSrgb(ColorUtils.srgbToLinear(srgbBefore)); + float[] srgbAfter = linearToSrgb(srgbToLinear(srgbBefore)); - int packedHslAfter = ColorUtils.srgbToPackedHsl(srgbAfter); + int packedHslAfter = srgbToPackedHsl(srgbAfter); if (packedHslBefore != packedHslAfter) { assertEquals(String.format( "Inaccurate color, packedHsl: %d\t->\t%d,\tHSL: %s\t->\t%s,\tRGB: %s\t->\t%s\n", packedHslBefore, packedHslAfter, Arrays.toString(hsl), - Arrays.toString(ColorUtils.unpackHsl(packedHslAfter)), + Arrays.toString(unpackHsl(packedHslAfter)), Arrays.toString(srgbBefore), Arrays.toString(srgbAfter) ), packedHslBefore, packedHslAfter); diff --git a/src/test/java/rs117/hd/utils/ExpressionParserTest.java b/src/test/java/rs117/hd/utils/ExpressionParserTest.java index 858f57c8b2..babc98aa6b 100644 --- a/src/test/java/rs117/hd/utils/ExpressionParserTest.java +++ b/src/test/java/rs117/hd/utils/ExpressionParserTest.java @@ -63,6 +63,7 @@ public static void main(String... args) { assertThrows(() -> parseExpression("(5 + ( missing paren)")); LinkedHashMap testCases = new LinkedHashMap<>(); + testCases.put("h != 0", true); testCases.put("s == 0 || h <= 10 && s < 2", false); testCases.put("h == 8 && (s == 3 || s == 4) && l >= 20", false); testCases.put("h > 3 && s < 15 && l < 21", true); diff --git a/src/test/java/rs117/hd/utils/Mat4Test.java b/src/test/java/rs117/hd/utils/Mat4Test.java new file mode 100644 index 0000000000..64c7269561 --- /dev/null +++ b/src/test/java/rs117/hd/utils/Mat4Test.java @@ -0,0 +1,53 @@ +package rs117.hd.utils; + +import junit.framework.TestCase; +import org.junit.Assert; + +public class Mat4Test extends TestCase { + public void testTranspose() { + float[] m = { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + }; + System.out.println(Mat4.format(m)); + Mat4.transpose(m); + System.out.println("\n" + Mat4.format(m)); + } + + public void testInverse() { + float[] nonInvertible = { + 1, -2, 0, 2, + -1, 3, 1, -2, + -1, 5, 3, -2, + 0, 7, 7, 0 + }; + Mat4.transpose(nonInvertible); + Assert.assertThrows(IllegalArgumentException.class, () -> Mat4.inverse(nonInvertible)); + + String identity = Mat4.format(Mat4.identity()); + + float[] invertible = { + 0, 0, -1, 2, + 0, 1, 0, 0, + 9, 0, 0, 0, + 0, 0, 0, 1 + }; + Mat4.transpose(invertible); + float[] inverse = Mat4.inverse(invertible); + Mat4.mul(inverse, invertible); + Assert.assertEquals(identity, Mat4.format(inverse)); + + invertible = new float[] { + 4, 0, 0, 0, + 0, 0, 2, 0, + 0, 1, 2, 0, + 1, 0, 0, 1 + }; + Mat4.transpose(invertible); + inverse = Mat4.inverse(invertible); + Mat4.mul(inverse, invertible); + Assert.assertEquals(identity, Mat4.format(inverse)); + } +} diff --git a/src/test/java/rs117/hd/utils/MatrixTest.java b/src/test/java/rs117/hd/utils/MatrixTest.java new file mode 100644 index 0000000000..dfadc7e96d --- /dev/null +++ b/src/test/java/rs117/hd/utils/MatrixTest.java @@ -0,0 +1,26 @@ +package rs117.hd.utils; + +import junit.framework.TestCase; + +public class MatrixTest extends TestCase { + public void testSolve() { + float[] invertible = { + 0, 0, -1, 2, + 0, 1, 0, 0, + 9, 0, 0, 0, + 0, 0, 0, 1 + }; + System.out.println(Matrix.format(invertible, 4, 4)); + Matrix.solve(invertible, 4, 4); + System.out.println(Matrix.format(invertible, 4, 4)); + + float[] linearSystem = { + -1382.59f, -1, 0, 0.11f, + 1180.23f, 0, 661.96f, 0.08f, + 661.96f, 0, -1180.23f, -0.11f + }; + System.out.println(Matrix.format(linearSystem, 3, 4)); + Matrix.solve(linearSystem, 3, 4); + System.out.println(Matrix.format(linearSystem, 3, 4)); + } +} diff --git a/src/test/resources/rs117/hd/test/lights.json b/src/test/resources/rs117/hd/test/lights.json deleted file mode 100644 index 6ddccd6eab..0000000000 --- a/src/test/resources/rs117/hd/test/lights.json +++ /dev/null @@ -1,21 +0,0 @@ -[ - { - "description": "SPIT_ROAST", - "height": 50, - "alignment": "CENTER", - "radius": 250, - "strength": 12.5, - "color": [ - 252, - 148, - 3 - ], - "type": "FLICKER", - "duration": 0, - "range": 20, - "objectIds": [ - 5608, - 4267 - ] - } -]