diff --git a/.gitignore b/.gitignore index e359520..53447f5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /mac/ /dist/ /installer/ +/vcpkg/ /client_distribution/ /server_distribution/ enc_temp_folder/ diff --git a/assets/opensb/lighting.config.patch b/assets/opensb/lighting.config.patch new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/assets/opensb/lighting.config.patch @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/assets/opensb/objects/opensb/object.patch.lua b/assets/opensb/objects/opensb/object.patch.lua new file mode 100644 index 0000000..2d70052 --- /dev/null +++ b/assets/opensb/objects/opensb/object.patch.lua @@ -0,0 +1,21 @@ +-- unused for now + +local function modLight(light) + for i = 1, #light do + light[i] = light[i] * 0.4 + end +end + +function patch(object, path) + if object.lightColor then + modLight(object.lightColor) + object.pointLight = true + return object; + elseif object.lightColors then + for i, v in pairs(object.lightColors) do + modLight(v) + end + object.pointLight = true + return object; + end +end \ No newline at end of file diff --git a/assets/opensb/rendering/effects/world.frag b/assets/opensb/rendering/effects/world.frag index 10bd1ad..7da662d 100644 --- a/assets/opensb/rendering/effects/world.frag +++ b/assets/opensb/rendering/effects/world.frag @@ -53,6 +53,15 @@ vec4 bicubicSample(sampler2D texture, vec2 texcoord, vec2 texscale) { mix(sample1, sample0, sx), sy); } +vec3 sampleLight(vec2 coord, vec2 scale) { + //soften super bright lights a little + const float threshold = 1.0; + vec3 rgb = bicubicSample(lightMap, coord, scale).rgb; + vec3 lower = min(rgb, threshold); + vec3 upper = max(rgb, threshold) - threshold; + return lower + (upper / (vec3(1.) + upper)); +} + void main() { vec4 texColor; if (fragmentTextureIndex > 2.9) { @@ -72,6 +81,6 @@ void main() { if (texColor.a == 0.99607843137) finalColor.a = fragmentColor.a; else if (lightMapEnabled && finalLightMapMultiplier > 0.0) - finalColor.rgb *= bicubicSample(lightMap, fragmentLightMapCoordinate, 1.0 / lightMapSize).rgb * finalLightMapMultiplier; + finalColor.rgb *= sampleLight(fragmentLightMapCoordinate, 1.0 / lightMapSize) * finalLightMapMultiplier; gl_FragColor = finalColor; } \ No newline at end of file diff --git a/assets/opensb/scripts/opensb/assets/postload.lua b/assets/opensb/scripts/opensb/assets/postload.lua index 8f84352..0b57b7a 100644 --- a/assets/opensb/scripts/opensb/assets/postload.lua +++ b/assets/opensb/scripts/opensb/assets/postload.lua @@ -1,4 +1,4 @@ --- revert cursor frames if a mod replaced cursors.png with a SD version again +-- Revert cursor frames if a mod replaced cursors.png with a SD version again if assets.image("/cursors/cursors.png"):size()[1] == 64 then local path = "/cursors/opensb/revert.cursor.patch" assets.add(path, '{"scale":null}') @@ -8,4 +8,11 @@ if assets.image("/cursors/cursors.png"):size()[1] == 64 then path = "/cursors/opensb/revert.frames.patch" assets.add(path, '{"frameGrid":{"size":[16,16]}}') assets.patch("/cursors/cursors.frames", path) -end \ No newline at end of file +end + +-- Add object patches +--local objects = assets.byExtension("object") +--local path = "/objects/opensb/object.patch.lua" +--for i = 1, #objects do +-- assets.patch(objects[i], path) +--end \ No newline at end of file diff --git a/assets/opensb/tiles/shadows.png b/assets/opensb/tiles/shadows.png new file mode 100644 index 0000000..fc34769 Binary files /dev/null and b/assets/opensb/tiles/shadows.png differ diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 2994a43..d0fb879 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -32,6 +32,13 @@ set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../cmake) set(CMAKE_CONFIGURATION_TYPES Debug RelWithAsserts RelWithDebInfo Release) set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") set(CMAKE_EXE_LINKER_FLAGS_RELWITHASSERTS "" CACHE STRING "" FORCE) + +#include(CheckIPOSupported) +#check_ipo_supported(RESULT lto_supported OUTPUT lto_output) +#if(lto_supported) +# set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +#endif() + # Update the docstring on CMAKE_BUILD_TYPE to show what options we actually # allow # SET (CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "Choose the type of build, options are: Debug RelWithAsserts RelWithDebInfo Release" FORCE) diff --git a/source/application/StarRenderer.hpp b/source/application/StarRenderer.hpp index 7b75077..77b53bd 100644 --- a/source/application/StarRenderer.hpp +++ b/source/application/StarRenderer.hpp @@ -141,8 +141,7 @@ public: // The effect config will specify named parameters and textures which can be // set here. virtual void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) = 0; - virtual void setEffectTexture(String const& textureName, Image const& image) = 0; - + virtual void setEffectTexture(String const& textureName, ImageView const& image) = 0; virtual bool switchEffectConfig(String const& name) = 0; // Any further rendering will be scissored based on this rect, specified in diff --git a/source/application/StarRenderer_opengl20.cpp b/source/application/StarRenderer_opengl20.cpp index ac141a4..ca29a70 100644 --- a/source/application/StarRenderer_opengl20.cpp +++ b/source/application/StarRenderer_opengl20.cpp @@ -118,7 +118,7 @@ Vec2U OpenGl20Renderer::screenSize() const { } OpenGl20Renderer::GlFrameBuffer::GlFrameBuffer(Json const& fbConfig) : config(fbConfig) { - texture = createGlTexture(Image(), TextureAddressing::Clamp, TextureFiltering::Nearest); + texture = createGlTexture(ImageView(), TextureAddressing::Clamp, TextureFiltering::Nearest); glBindTexture(GL_TEXTURE_2D, texture->glTextureId()); Vec2U size = jsonToVec2U(config.getArray("size", { 256, 256 })); @@ -147,13 +147,15 @@ void OpenGl20Renderer::loadConfig(Json const& config) { for (auto& pair : config.getObject("frameBuffers", {})) m_frameBuffers[pair.first] = make_ref(pair.second); + + setScreenSize(m_screenSize); } void OpenGl20Renderer::loadEffectConfig(String const& name, Json const& effectConfig, StringMap const& shaders) { - if (m_effects.contains(name)) { - Logger::warn("OpenGL effect {} already exists", name); - switchEffectConfig(name); - return; + if (auto effect = m_effects.ptr(name)) { + Logger::info("Reloading OpenGL effect {}", name); + glDeleteProgram(effect->program); + m_effects.erase(name); } GLint status = 0; @@ -177,8 +179,18 @@ void OpenGl20Renderer::loadEffectConfig(String const& name, Json const& effectCo return shader; }; - GLuint vertexShader = compileShader(GL_VERTEX_SHADER, "vertex"); - GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, "fragment"); + GLuint vertexShader = 0, fragmentShader = 0; + try { + vertexShader = compileShader(GL_VERTEX_SHADER, "vertex"); + fragmentShader = compileShader(GL_FRAGMENT_SHADER, "fragment"); + } + catch (RendererException const& e) { + Logger::error("Shader compile error, using default: {}", e.what()); + if (vertexShader) glDeleteShader(vertexShader); + if (fragmentShader) glDeleteShader(fragmentShader); + vertexShader = compileShader(GL_VERTEX_SHADER, DefaultVertexShader); + fragmentShader = compileShader(GL_FRAGMENT_SHADER, DefaultFragmentShader); + } GLuint program = glCreateProgram(); @@ -308,7 +320,7 @@ void OpenGl20Renderer::setEffectParameter(String const& parameterName, RenderEff ptr->parameterValue = value; } -void OpenGl20Renderer::setEffectTexture(String const& textureName, Image const& image) { +void OpenGl20Renderer::setEffectTexture(String const& textureName, ImageView const& image) { auto ptr = m_currentEffect->textures.ptr(textureName); if (!ptr) return; @@ -319,8 +331,8 @@ void OpenGl20Renderer::setEffectTexture(String const& textureName, Image const& ptr->textureValue = createGlTexture(image, ptr->textureAddressing, ptr->textureFiltering); } else { glBindTexture(GL_TEXTURE_2D, ptr->textureValue->textureId); - ptr->textureValue->textureSize = image.size(); - uploadTextureImage(image.pixelFormat(), image.size(), image.data()); + ptr->textureValue->textureSize = image.size; + uploadTextureImage(image.format, image.size, image.data); } if (ptr->textureSizeUniform != -1) { @@ -789,7 +801,9 @@ bool OpenGl20Renderer::logGlErrorSummary(String prefix) { void OpenGl20Renderer::uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data) { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + Maybe internalFormat; GLenum format; + GLenum type = GL_UNSIGNED_BYTE; if (pixelFormat == PixelFormat::RGB24) format = GL_RGB; else if (pixelFormat == PixelFormat::RGBA32) @@ -798,10 +812,19 @@ void OpenGl20Renderer::uploadTextureImage(PixelFormat pixelFormat, Vec2U size, u format = GL_BGR; else if (pixelFormat == PixelFormat::BGRA32) format = GL_BGRA; - else - throw RendererException("Unsupported texture format in OpenGL20Renderer::uploadTextureImage"); + else { + type = GL_FLOAT; + if (pixelFormat == PixelFormat::RGB_F) { + internalFormat = GL_RGB32F; + format = GL_RGB; + } else if (pixelFormat == PixelFormat::RGBA_F) { + internalFormat = GL_RGBA32F; + format = GL_RGBA; + } else + throw RendererException("Unsupported texture format in OpenGL20Renderer::uploadTextureImage"); + } - glTexImage2D(GL_TEXTURE_2D, 0, format, size[0], size[1], 0, format, GL_UNSIGNED_BYTE, data); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat.value(format), size[0], size[1], 0, format, type, data); } void OpenGl20Renderer::flushImmediatePrimitives() { @@ -813,12 +836,12 @@ void OpenGl20Renderer::flushImmediatePrimitives() { renderGlBuffer(*m_immediateRenderBuffer, Mat3F::identity()); } -auto OpenGl20Renderer::createGlTexture(Image const& image, TextureAddressing addressing, TextureFiltering filtering) - -> RefPtr { +auto OpenGl20Renderer::createGlTexture(ImageView const& image, TextureAddressing addressing, TextureFiltering filtering) + ->RefPtr { auto glLoneTexture = make_ref(); glLoneTexture->textureFiltering = filtering; glLoneTexture->textureAddressing = addressing; - glLoneTexture->textureSize = image.size(); + glLoneTexture->textureSize = image.size; glGenTextures(1, &glLoneTexture->textureId); if (glLoneTexture->textureId == 0) @@ -844,7 +867,7 @@ auto OpenGl20Renderer::createGlTexture(Image const& image, TextureAddressing add if (!image.empty()) - uploadTextureImage(image.pixelFormat(), image.size(), image.data()); + uploadTextureImage(image.format, image.size, image.data); return glLoneTexture; } diff --git a/source/application/StarRenderer_opengl20.hpp b/source/application/StarRenderer_opengl20.hpp index 8c96775..fa406b6 100644 --- a/source/application/StarRenderer_opengl20.hpp +++ b/source/application/StarRenderer_opengl20.hpp @@ -25,7 +25,7 @@ public: void loadEffectConfig(String const& name, Json const& effectConfig, StringMap const& shaders) override; void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) override; - void setEffectTexture(String const& textureName, Image const& image) override; + void setEffectTexture(String const& textureName, ImageView const& image) override; void setScissorRect(Maybe const& scissorRect) override; @@ -188,7 +188,8 @@ private: static bool logGlErrorSummary(String prefix); static void uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data); - static RefPtr createGlTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering); + + static RefPtr createGlTexture(ImageView const& image, TextureAddressing addressing, TextureFiltering filtering); shared_ptr createGlRenderBuffer(); diff --git a/source/base/CMakeLists.txt b/source/base/CMakeLists.txt index f16257a..8f4d18b 100644 --- a/source/base/CMakeLists.txt +++ b/source/base/CMakeLists.txt @@ -25,6 +25,7 @@ SET (star_base_HEADERS SET (star_base_SOURCES StarAnimatedPartSet.cpp StarAssets.cpp + StarCellularLightArray.cpp StarCellularLighting.cpp StarConfiguration.cpp StarDirectoryAssetSource.cpp diff --git a/source/base/StarAssets.cpp b/source/base/StarAssets.cpp index 6593d3d..2bbe509 100644 --- a/source/base/StarAssets.cpp +++ b/source/base/StarAssets.cpp @@ -110,8 +110,15 @@ Assets::Assets(Settings settings, StringList assetSources) { m_assetSources = std::move(assetSources); auto luaEngine = LuaEngine::create(); + m_luaEngine = luaEngine; + auto pushGlobalContext = [&luaEngine](String const& name, LuaCallbacks & callbacks) { + auto table = luaEngine->createTable(); + for (auto const& p : callbacks.callbacks()) + table.set(p.first, luaEngine->createWrappedFunction(p.second)); + luaEngine->setGlobal(name, table); + }; + pushGlobalContext("sb", LuaBindings::makeUtilityCallbacks()); auto decorateLuaContext = [this](LuaContext& context, MemoryAssetSourcePtr newFiles) { - context.setCallbacks("sb", LuaBindings::makeUtilityCallbacks()); LuaCallbacks callbacks; callbacks.registerCallbackWithSignature("byExtension", bind(&Assets::scanExtension, this, _1)); callbacks.registerCallbackWithSignature("json", bind(&Assets::json, this, _1)); @@ -133,38 +140,40 @@ Assets::Assets(Settings settings, StringList assetSources) { return b ? scan(a.value(), *b) : scan(a.value()); }); - callbacks.registerCallback("add", [this, &newFiles](LuaEngine& engine, String const& path, LuaValue const& data) { - ByteArray bytes; - if (auto str = engine.luaMaybeTo(data)) - bytes = ByteArray(str->utf8Ptr(), str->utf8Size()); - else { - auto json = engine.luaTo(data).repr(); - bytes = ByteArray(json.utf8Ptr(), json.utf8Size()); - } - newFiles->set(path, bytes); - }); + if (newFiles) { + callbacks.registerCallback("add", [this, &newFiles](LuaEngine& engine, String const& path, LuaValue const& data) { + ByteArray bytes; + if (auto str = engine.luaMaybeTo(data)) + bytes = ByteArray(str->utf8Ptr(), str->utf8Size()); + else { + auto json = engine.luaTo(data).repr(); + bytes = ByteArray(json.utf8Ptr(), json.utf8Size()); + } + newFiles->set(path, bytes); + }); - callbacks.registerCallback("patch", [this, &newFiles](String const& path, String const& patchPath) -> bool { - if (auto file = m_files.ptr(path)) { - if (newFiles->contains(patchPath)) { - file->patchSources.append(make_pair(patchPath, newFiles)); - return true; - } else { - if (auto asset = m_files.ptr(patchPath)) { - file->patchSources.append(make_pair(patchPath, asset->source)); + callbacks.registerCallback("patch", [this, &newFiles](String const& path, String const& patchPath) -> bool { + if (auto file = m_files.ptr(path)) { + if (newFiles->contains(patchPath)) { + file->patchSources.append(make_pair(patchPath, newFiles)); return true; + } else { + if (auto asset = m_files.ptr(patchPath)) { + file->patchSources.append(make_pair(patchPath, asset->source)); + return true; + } } } - } - return false; - }); + return false; + }); - callbacks.registerCallback("erase", [this](String const& path) -> bool { - bool erased = m_files.erase(path); - if (erased) - m_filesByExtension[AssetPath::extension(path).toLower()].erase(path); - return erased; - }); + callbacks.registerCallback("erase", [this](String const& path) -> bool { + bool erased = m_files.erase(path); + if (erased) + m_filesByExtension[AssetPath::extension(path).toLower()].erase(path); + return erased; + }); + } context.setCallbacks("assets", callbacks); }; @@ -883,21 +892,35 @@ Json Assets::readJson(String const& path) const { try { Json result = inputUtf8Json(streamData.begin(), streamData.end(), false); for (auto const& pair : m_files.get(path).patchSources) { - auto patchStream = pair.second->read(pair.first); - auto patchJson = inputUtf8Json(patchStream.begin(), patchStream.end(), false); - if (patchJson.isType(Json::Type::Array)) { - auto patchData = patchJson.toArray(); - try { - result = checkPatchArray(pair.first, pair.second, result, patchData, {}); - } catch (JsonPatchTestFail const& e) { - Logger::debug("Patch test failure from file {} in source: '{}' at '{}'. Caused by: {}", pair.first, pair.second->metadata().value("name", ""), m_assetSourcePaths.getLeft(pair.second), e.what()); - } catch (JsonPatchException const& e) { - Logger::error("Could not apply patch from file {} in source: '{}' at '{}'. Caused by: {}", pair.first, pair.second->metadata().value("name", ""), m_assetSourcePaths.getLeft(pair.second), e.what()); + auto& patchPath = pair.first; + auto& patchSource = pair.second; + auto patchStream = patchSource->read(patchPath); + if (pair.first.endsWith(".lua")) { + MutexLocker luaLocker(m_luaMutex); + // Kae: i don't like that lock. perhaps have a LuaEngine and patch context cache per worker thread later on? + LuaContextPtr& context = m_patchContexts[patchPath]; + if (!context) { + context = make_shared(as(m_luaEngine.get())->createContext()); + context->load(patchStream, patchPath); + } + auto newResult = context->invokePath("patch", result, path); + if (newResult) + result = std::move(newResult); + } else { + auto patchJson = inputUtf8Json(patchStream.begin(), patchStream.end(), false); + if (patchJson.isType(Json::Type::Array)) { + auto patchData = patchJson.toArray(); + try { + result = checkPatchArray(patchPath, patchSource, result, patchData, {}); + } catch (JsonPatchTestFail const& e) { + Logger::debug("Patch test failure from file {} in source: '{}' at '{}'. Caused by: {}", patchPath, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what()); + } catch (JsonPatchException const& e) { + Logger::error("Could not apply patch from file {} in source: '{}' at '{}'. Caused by: {}", patchPath, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what()); + } + } else if (patchJson.isType(Json::Type::Object)) {//Kae: Do a good ol' json merge instead if the .patch file is a Json object + result = jsonMergeNulling(result, patchJson.toObject()); + } } - } else if (patchJson.isType(Json::Type::Object)) { //Kae: Do a good ol' json merge instead if the .patch file is a Json object - auto patchData = patchJson.toObject(); - result = jsonMergeNulling(result, patchData); - } } return result; } catch (std::exception const& e) { diff --git a/source/base/StarAssets.hpp b/source/base/StarAssets.hpp index f945cf8..8cfbc52 100644 --- a/source/base/StarAssets.hpp +++ b/source/base/StarAssets.hpp @@ -7,6 +7,7 @@ #include "StarThread.hpp" #include "StarAssetSource.hpp" #include "StarAssetPath.hpp" +#include "StarRefPtr.hpp" namespace Star { @@ -16,6 +17,8 @@ STAR_CLASS(Image); STAR_STRUCT(FramesSpecification); STAR_CLASS(Assets); +STAR_CLASS(LuaContext); + STAR_EXCEPTION(AssetException, StarException); // The contents of an assets .frames file, which can be associated with one or @@ -313,6 +316,11 @@ private: mutable StringMap m_bestFramesFiles; mutable StringMap m_framesSpecifications; + // Lua + RefPtr m_luaEngine; // dumb but to avoid including Lua.hpp in here... + mutable StringMap m_patchContexts; + mutable Mutex m_luaMutex; + // Paths of all used asset sources, in load order. StringList m_assetSources; diff --git a/source/base/StarCellularLightArray.cpp b/source/base/StarCellularLightArray.cpp new file mode 100644 index 0000000..60a6d95 --- /dev/null +++ b/source/base/StarCellularLightArray.cpp @@ -0,0 +1,130 @@ +#include "StarCellularLightArray.hpp" +// just specializing these in a cpp file so I can iterate on them without recompiling like 40 files!! + +namespace Star { + +template <> +void CellularLightArray::calculatePointLighting(size_t xmin, size_t ymin, size_t xmax, size_t ymax) { + float perBlockObstacleAttenuation = 1.0f / m_pointMaxObstacle; + float perBlockAirAttenuation = 1.0f / m_pointMaxAir; + + for (PointLight light : m_pointLights) { + if (light.position[0] < 0 || light.position[0] > m_width - 1 || light.position[1] < 0 || light.position[1] > m_height - 1) + continue; + + float maxIntensity = ScalarLightTraits::maxIntensity(light.value); + Vec2F beamDirection = Vec2F(1, 0).rotate(light.beamAngle); + + float maxRange = maxIntensity * m_pointMaxAir; + // The min / max considering the radius of the light + size_t lxmin = std::floor(std::max(xmin, light.position[0] - maxRange)); + size_t lymin = std::floor(std::max(ymin, light.position[1] - maxRange)); + size_t lxmax = std::ceil(std::min(xmax, light.position[0] + maxRange)); + size_t lymax = std::ceil(std::min(ymax, light.position[1] + maxRange)); + + for (size_t x = lxmin; x < lxmax; ++x) { + for (size_t y = lymin; y < lymax; ++y) { + LightValue lvalue = getLight(x, y); + // + 0.5f to correct block position to center + Vec2F blockPos = Vec2F(x + 0.5f, y + 0.5f); + + Vec2F relativeLightPosition = blockPos - light.position; + float distance = relativeLightPosition.magnitude(); + if (distance == 0.0f) { + setLight(x, y, light.value + lvalue); + continue; + } + + float attenuation = distance * perBlockAirAttenuation; + if (attenuation >= 1.0f) + continue; + + Vec2F direction = relativeLightPosition / distance; + if (light.beam > 0.0f) { + attenuation += (1.0f - light.beamAmbience) * clamp(light.beam * (1.0f - direction * beamDirection), 0.0f, 1.0f); + if (attenuation >= 1.0f) + continue; + } + + float remainingAttenuation = maxIntensity - attenuation; + if (remainingAttenuation <= 0.0f) + continue; + + // Need to circularize manhattan attenuation here + float circularizedPerBlockObstacleAttenuation = perBlockObstacleAttenuation / max(fabs(direction[0]), fabs(direction[1])); + float blockAttenuation = lineAttenuation(blockPos, light.position, circularizedPerBlockObstacleAttenuation, remainingAttenuation); + + // Apply single obstacle boost (determine single obstacle by one + // block unit of attenuation). + attenuation += blockAttenuation + min(blockAttenuation, circularizedPerBlockObstacleAttenuation) * m_pointObstacleBoost; + + if (attenuation < 1.0f) + setLight(x, y, lvalue + ScalarLightTraits::subtract(light.value, attenuation)); + } + } + } +} + +template <> +void CellularLightArray::calculatePointLighting(size_t xmin, size_t ymin, size_t xmax, size_t ymax) { + float perBlockObstacleAttenuation = 1.0f / m_pointMaxObstacle; + float perBlockAirAttenuation = 1.0f / m_pointMaxAir; + + for (PointLight light : m_pointLights) { + if (light.position[0] < 0 || light.position[0] > m_width - 1 || light.position[1] < 0 || light.position[1] > m_height - 1) + continue; + + float maxIntensity = ColoredLightTraits::maxIntensity(light.value); + Vec2F beamDirection = Vec2F(1, 0).rotate(light.beamAngle); + + float maxRange = maxIntensity * m_pointMaxAir; + // The min / max considering the radius of the light + size_t lxmin = std::floor(std::max(xmin, light.position[0] - maxRange)); + size_t lymin = std::floor(std::max(ymin, light.position[1] - maxRange)); + size_t lxmax = std::ceil(std::min(xmax, light.position[0] + maxRange)); + size_t lymax = std::ceil(std::min(ymax, light.position[1] + maxRange)); + + for (size_t x = lxmin; x < lxmax; ++x) { + for (size_t y = lymin; y < lymax; ++y) { + LightValue lvalue = getLight(x, y); + // + 0.5f to correct block position to center + Vec2F blockPos = Vec2F(x + 0.5f, y + 0.5f); + + Vec2F relativeLightPosition = blockPos - light.position; + float distance = relativeLightPosition.magnitude(); + if (distance == 0.0f) { + setLight(x, y, light.value + lvalue); + continue; + } + + float attenuation = distance * perBlockAirAttenuation; + if (attenuation >= 1.0f) + continue; + + Vec2F direction = relativeLightPosition / distance; + if (light.beam > 0.0f) { + attenuation += (1.0f - light.beamAmbience) * clamp(light.beam * (1.0f - direction * beamDirection), 0.0f, 1.0f); + if (attenuation >= 1.0f) + continue; + } + + float remainingAttenuation = maxIntensity - attenuation; + if (remainingAttenuation <= 0.0f) + continue; + + // Need to circularize manhattan attenuation here + float circularizedPerBlockObstacleAttenuation = perBlockObstacleAttenuation / max(fabs(direction[0]), fabs(direction[1])); + float blockAttenuation = lineAttenuation(blockPos, light.position, circularizedPerBlockObstacleAttenuation, remainingAttenuation); + + // Apply single obstacle boost (determine single obstacle by one + // block unit of attenuation). + attenuation += blockAttenuation + min(blockAttenuation, circularizedPerBlockObstacleAttenuation) * m_pointObstacleBoost; + + if (attenuation < 1.0f) + setLight(x, y, lvalue + ColoredLightTraits::subtract(light.value, attenuation)); + } + } + } +} + +} \ No newline at end of file diff --git a/source/base/StarCellularLightArray.hpp b/source/base/StarCellularLightArray.hpp index d73cacf..225d42b 100644 --- a/source/base/StarCellularLightArray.hpp +++ b/source/base/StarCellularLightArray.hpp @@ -11,6 +11,7 @@ struct ScalarLightTraits { static float spread(float source, float dest, float drop); static float subtract(float value, float drop); + static float multiply(float v1, float v2); static float maxIntensity(float value); static float minIntensity(float value); @@ -26,6 +27,7 @@ struct ColoredLightTraits { static Vec3F spread(Vec3F const& source, Vec3F const& dest, float drop); static Vec3F subtract(Vec3F value, float drop); + static Vec3F multiply(Vec3F value, float drop); static float maxIntensity(Vec3F const& value); static float minIntensity(Vec3F const& value); @@ -139,6 +141,10 @@ inline float ScalarLightTraits::subtract(float c, float drop) { return std::max(c - drop, 0.0f); } +inline float ScalarLightTraits::multiply(float v1, float v2) { + return v1 * v2; +} + inline float ScalarLightTraits::maxIntensity(float value) { return value; } @@ -179,6 +185,10 @@ inline Vec3F ColoredLightTraits::subtract(Vec3F c, float drop) { return c; } +inline Vec3F ColoredLightTraits::multiply(Vec3F c, float drop) { + return c * drop; +} + inline float ColoredLightTraits::maxIntensity(Vec3F const& value) { return value.max(); } @@ -387,68 +397,6 @@ void CellularLightArray::calculateLightSpread(size_t xMin, size_t y } } -template -void CellularLightArray::calculatePointLighting(size_t xmin, size_t ymin, size_t xmax, size_t ymax) { - float perBlockObstacleAttenuation = 1.0f / m_pointMaxObstacle; - float perBlockAirAttenuation = 1.0f / m_pointMaxAir; - - for (PointLight light : m_pointLights) { - if (light.position[0] < 0 || light.position[0] > m_width - 1 || light.position[1] < 0 || light.position[1] > m_height - 1) - continue; - - float maxIntensity = LightTraits::maxIntensity(light.value); - Vec2F beamDirection = Vec2F(1, 0).rotate(light.beamAngle); - - float maxRange = maxIntensity * m_pointMaxAir; - // The min / max considering the radius of the light - size_t lxmin = std::floor(std::max(xmin, light.position[0] - maxRange)); - size_t lymin = std::floor(std::max(ymin, light.position[1] - maxRange)); - size_t lxmax = std::ceil(std::min(xmax, light.position[0] + maxRange)); - size_t lymax = std::ceil(std::min(ymax, light.position[1] + maxRange)); - - for (size_t x = lxmin; x < lxmax; ++x) { - for (size_t y = lymin; y < lymax; ++y) { - LightValue lvalue = getLight(x, y); - // + 0.5f to correct block position to center - Vec2F blockPos = Vec2F(x + 0.5f, y + 0.5f); - - Vec2F relativeLightPosition = blockPos - light.position; - float distance = relativeLightPosition.magnitude(); - if (distance == 0.0f) { - setLight(x, y, LightTraits::max(light.value, lvalue)); - continue; - } - - float attenuation = distance * perBlockAirAttenuation; - if (attenuation >= 1.0f) - continue; - - Vec2F direction = relativeLightPosition / distance; - if (light.beam > 0.0f) { - attenuation += (1.0f - light.beamAmbience) * clamp(light.beam * (1.0f - direction * beamDirection), 0.0f, 1.0f); - if (attenuation >= 1.0f) - continue; - } - - float remainingAttenuation = maxIntensity - LightTraits::minIntensity(lvalue) - attenuation; - if (remainingAttenuation <= 0.0f) - continue; - - // Need to circularize manhattan attenuation here - float circularizedPerBlockObstacleAttenuation = perBlockObstacleAttenuation / max(fabs(direction[0]), fabs(direction[1])); - float blockAttenuation = lineAttenuation(blockPos, light.position, circularizedPerBlockObstacleAttenuation, remainingAttenuation); - - // Apply single obstacle boost (determine single obstacle by one - // block unit of attenuation). - attenuation += blockAttenuation + min(blockAttenuation, circularizedPerBlockObstacleAttenuation) * m_pointObstacleBoost; - - if (attenuation < 1.0f) - setLight(x, y, LightTraits::max(LightTraits::subtract(light.value, attenuation), lvalue)); - } - } - } -} - template float CellularLightArray::lineAttenuation(Vec2F const& start, Vec2F const& end, float perObstacleAttenuation, float maxAttenuation) { diff --git a/source/base/StarCellularLighting.cpp b/source/base/StarCellularLighting.cpp index c0a92ea..579f48a 100644 --- a/source/base/StarCellularLighting.cpp +++ b/source/base/StarCellularLighting.cpp @@ -2,6 +2,45 @@ namespace Star { +Lightmap::Lightmap() : m_width(0), m_height(0) {} + +Lightmap::Lightmap(unsigned width, unsigned height) : m_width(width), m_height(height) { + m_data = std::make_unique(len()); +} + +Lightmap::Lightmap(Lightmap const& lightMap) { + operator=(lightMap); +} + +Lightmap::Lightmap(Lightmap&& lightMap) noexcept { + operator=(std::move(lightMap)); +} + +Lightmap& Lightmap::operator=(Lightmap const& lightMap) { + m_width = lightMap.m_width; + m_height = lightMap.m_height; + if (lightMap.m_data) { + m_data = std::make_unique(len()); + memcpy(m_data.get(), lightMap.m_data.get(), len()); + } + return *this; +} + +Lightmap& Lightmap::operator=(Lightmap&& lightMap) noexcept { + m_width = take(lightMap.m_width); + m_height = take(lightMap.m_height); + m_data = take(lightMap.m_data); + return *this; +} + +Lightmap::operator ImageView() { + ImageView view; + view.data = (uint8_t*)m_data.get(); + view.size = size(); + view.format = PixelFormat::RGB_F; + return view; +} + CellularLightingCalculator::CellularLightingCalculator(bool monochrome) : m_monochrome(monochrome) { @@ -104,6 +143,32 @@ void CellularLightingCalculator::calculate(Image& output) { } } +void CellularLightingCalculator::calculate(Lightmap& output) { + Vec2S arrayMin = Vec2S(m_queryRegion.min() - m_calculationRegion.min()); + Vec2S arrayMax = Vec2S(m_queryRegion.max() - m_calculationRegion.min()); + + if (m_monochrome) + m_lightArray.right().calculate(arrayMin[0], arrayMin[1], arrayMax[0], arrayMax[1]); + else + m_lightArray.left().calculate(arrayMin[0], arrayMin[1], arrayMax[0], arrayMax[1]); + + output = Lightmap(arrayMax[0] - arrayMin[0], arrayMax[1] - arrayMin[1]); + + if (m_monochrome) { + for (size_t x = arrayMin[0]; x < arrayMax[0]; ++x) { + for (size_t y = arrayMin[1]; y < arrayMax[1]; ++y) { + output.set(x - arrayMin[0], y - arrayMin[1], m_lightArray.right().getLight(x, y)); + } + } + } else { + for (size_t x = arrayMin[0]; x < arrayMax[0]; ++x) { + for (size_t y = arrayMin[1]; y < arrayMax[1]; ++y) { + output.set(x - arrayMin[0], y - arrayMin[1], m_lightArray.left().getLight(x, y)); + } + } + } +} + void CellularLightingCalculator::setupImage(Image& image, PixelFormat format) const { Vec2S arrayMin = Vec2S(m_queryRegion.min() - m_calculationRegion.min()); Vec2S arrayMax = Vec2S(m_queryRegion.max() - m_calculationRegion.min()); diff --git a/source/base/StarCellularLighting.hpp b/source/base/StarCellularLighting.hpp index caa52ae..7cb230f 100644 --- a/source/base/StarCellularLighting.hpp +++ b/source/base/StarCellularLighting.hpp @@ -11,6 +11,105 @@ namespace Star { +STAR_EXCEPTION(LightmapException, StarException); + +class Lightmap { +public: + Lightmap(); + Lightmap(unsigned width, unsigned height); + Lightmap(Lightmap const& lightMap); + Lightmap(Lightmap&& lightMap) noexcept; + + Lightmap& operator=(Lightmap const& lightMap); + Lightmap& operator=(Lightmap&& lightMap) noexcept; + + operator ImageView(); + + void set(unsigned x, unsigned y, float v); + void set(unsigned x, unsigned y, Vec3F const& v); + void add(unsigned x, unsigned y, Vec3F const& v); + Vec3F get(unsigned x, unsigned y) const; + + bool empty() const; + + Vec2U size() const; + unsigned width() const; + unsigned height() const; + float* data(); + +private: + size_t len() const; + + std::unique_ptr m_data; + unsigned m_width; + unsigned m_height; +}; + +inline void Lightmap::set(unsigned x, unsigned y, float v) { + if (x >= m_width || y >= m_height) { + throw LightmapException(strf("[{}, {}] out of range in Lightmap::set", x, y)); + return; + } + float* ptr = m_data.get() + (y * m_width * 3 + x * 3); + ptr[0] = ptr[1] = ptr[2] = v; +} + +inline void Lightmap::set(unsigned x, unsigned y, Vec3F const& v) { + if (x >= m_width || y >= m_height) { + throw LightmapException(strf("[{}, {}] out of range in Lightmap::set", x, y)); + return; + } + float* ptr = m_data.get() + (y * m_width * 3 + x * 3); + ptr[0] = v.x(); + ptr[1] = v.y(); + ptr[2] = v.z(); +} + +inline void Lightmap::add(unsigned x, unsigned y, Vec3F const& v) { + if (x >= m_width || y >= m_height) { + throw LightmapException(strf("[{}, {}] out of range in Lightmap::add", x, y)); + return; + } + float* ptr = m_data.get() + (y * m_width * 3 + x * 3); + ptr[0] += v.x(); + ptr[1] += v.y(); + ptr[2] += v.z(); +} + +inline Vec3F Lightmap::get(unsigned x, unsigned y) const { + if (x >= m_width || y >= m_height) { + throw LightmapException(strf("[{}, {}] out of range in Lightmap::get", x, y)); + return Vec3F(); + } + float* ptr = m_data.get() + (y * m_width * 3 + x * 3); + return Vec3F(ptr[0], ptr[1], ptr[2]); +} + + +inline bool Lightmap::empty() const { + return m_width == 0 || m_height == 0; +} + +inline Vec2U Lightmap::size() const { + return { m_width, m_height }; +} + +inline unsigned Lightmap::width() const { + return m_width; +} + +inline unsigned Lightmap::height() const { + return m_height; +} + +inline float* Lightmap::data() { + return m_data.get(); +} + +inline size_t Lightmap::len() const { + return m_width * m_height * 3; +} + // Produce lighting values from an integral cellular grid. Allows for floating // positional point and cellular light sources, as well as pre-lighting cells // individually. @@ -43,6 +142,8 @@ public: // output image. The image will be reset to the size of the region given in // the call to 'begin', and formatted as RGB24. void calculate(Image& output); + // Same as above, but the color data in a float buffer instead. + void calculate(Lightmap& output); void setupImage(Image& image, PixelFormat format = PixelFormat::RGB24) const; private: diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index 54c78b3..e97a8b7 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -228,35 +228,8 @@ void ClientApplication::applicationInit(ApplicationControllerPtr appController) void ClientApplication::renderInit(RendererPtr renderer) { Application::renderInit(renderer); - auto assets = m_root->assets(); - - auto loadEffectConfig = [&](String const& name) { - String path = strf("/rendering/effects/{}.config", name); - if (assets->assetExists(path)) { - StringMap shaders; - auto config = assets->json(path); - auto shaderConfig = config.getObject("effectShaders"); - for (auto& entry : shaderConfig) { - if (entry.second.isType(Json::Type::String)) { - String shader = entry.second.toString(); - if (!shader.hasChar('\n')) { - auto shaderBytes = assets->bytes(AssetPath::relativeTo(path, shader)); - shader = std::string(shaderBytes->ptr(), shaderBytes->size()); - } - shaders[entry.first] = shader; - } - } - - renderer->loadEffectConfig(name, config, shaders); - } - else - Logger::warn("No rendering config found for renderer with id '{}'", renderer->rendererId()); - }; - - renderer->loadConfig(assets->json("/rendering/opengl20.config")); - - loadEffectConfig("world"); - loadEffectConfig("interface"); + renderReload(); + m_root->registerReloadListener(m_reloadListener = make_shared([this]() { renderReload(); })); if (m_root->configuration()->get("limitTextureAtlasSize").optBool().value(false)) renderer->setSizeLimitEnabled(true); @@ -427,12 +400,7 @@ void ClientApplication::render() { auto paintStart = Time::monotonicMicroseconds(); m_worldPainter->render(m_renderData, [&]() -> bool { - if (auto newMinPosition = worldClient->waitForLighting(&m_renderData.lightMap)) { - m_renderData.lightMinPosition = *newMinPosition; - return true; - } else { - return false; - } + return worldClient->waitForLighting(&m_renderData); }); LogMap::set("client_render_world_painter", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - paintStart)); LogMap::set("client_render_world_total", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - totalStart)); @@ -458,6 +426,38 @@ void ClientApplication::getAudioData(int16_t* sampleData, size_t frameCount) { } } +void ClientApplication::renderReload() { + auto assets = m_root->assets(); + auto renderer = Application::renderer(); + + auto loadEffectConfig = [&](String const& name) { + String path = strf("/rendering/effects/{}.config", name); + if (assets->assetExists(path)) { + StringMap shaders; + auto config = assets->json(path); + auto shaderConfig = config.getObject("effectShaders"); + for (auto& entry : shaderConfig) { + if (entry.second.isType(Json::Type::String)) { + String shader = entry.second.toString(); + if (!shader.hasChar('\n')) { + auto shaderBytes = assets->bytes(AssetPath::relativeTo(path, shader)); + shader = std::string(shaderBytes->ptr(), shaderBytes->size()); + } + shaders[entry.first] = shader; + } + } + + renderer->loadEffectConfig(name, config, shaders); + } else + Logger::warn("No rendering config found for renderer with id '{}'", renderer->rendererId()); + }; + + renderer->loadConfig(assets->json("/rendering/opengl20.config")); + + loadEffectConfig("world"); + loadEffectConfig("interface"); +} + void ClientApplication::changeState(MainAppState newState) { MainAppState oldState = m_state; m_state = newState; diff --git a/source/client/StarClientApplication.hpp b/source/client/StarClientApplication.hpp index 6f80349..5670595 100644 --- a/source/client/StarClientApplication.hpp +++ b/source/client/StarClientApplication.hpp @@ -53,6 +53,8 @@ private: String password; }; + void renderReload(); + void changeState(MainAppState newState); void setError(String const& error); void setError(String const& error, std::exception const& e); @@ -71,6 +73,8 @@ private: RootUPtr m_root; ThreadFunction m_rootLoader; + CallbackListenerPtr m_reloadListener; + MainAppState m_state = MainAppState::Startup; // Valid after applicationInit is called diff --git a/source/core/StarImage.cpp b/source/core/StarImage.cpp index 0147369..82e3b05 100644 --- a/source/core/StarImage.cpp +++ b/source/core/StarImage.cpp @@ -518,4 +518,10 @@ void Image::writePng(IODevicePtr device) const { png_destroy_write_struct(&png_ptr, &info_ptr); } +ImageView::ImageView(Image const& image) { + size = image.size(); + data = image.data(); + format = image.pixelFormat(); +} + } diff --git a/source/core/StarImage.hpp b/source/core/StarImage.hpp index 6f186df..478d074 100644 --- a/source/core/StarImage.hpp +++ b/source/core/StarImage.hpp @@ -6,11 +6,13 @@ namespace Star { -enum class PixelFormat { +enum class PixelFormat : uint8_t { RGB24, RGBA32, BGR24, - BGRA32 + BGRA32, + RGB_F, + RGBA_F }; uint8_t bitsPerPixel(PixelFormat pf); @@ -148,8 +150,12 @@ inline uint8_t bitsPerPixel(PixelFormat pf) { return 32; case PixelFormat::BGR24: return 24; - default: + case PixelFormat::BGRA32: return 32; + case PixelFormat::RGB_F: + return 96; + default: + return 128; } } @@ -161,8 +167,12 @@ inline uint8_t bytesPerPixel(PixelFormat pf) { return 4; case PixelFormat::BGR24: return 3; - default: + case PixelFormat::BGRA32: return 4; + case PixelFormat::RGB_F: + return 12; + default: + return 16; } } @@ -307,4 +317,14 @@ void Image::forEachPixel(CallbackType&& callback) { } } +struct ImageView { + inline bool empty() const { return size.x() == 0 || size.y() == 0; } + ImageView() = default; + ImageView(Image const& image); + + Vec2U size{0, 0}; + uint8_t const* data = nullptr; + PixelFormat format = PixelFormat::RGB24; +}; + } diff --git a/source/core/StarJsonExtra.cpp b/source/core/StarJsonExtra.cpp index dcf2bf6..2517b7b 100644 --- a/source/core/StarJsonExtra.cpp +++ b/source/core/StarJsonExtra.cpp @@ -214,7 +214,6 @@ Color jsonToColor(Json const& v) { if (v.type() != Json::Type::Array || (v.size() != 3 && v.size() != 4)) throw JsonException("Json not an array of size 3 or 4 in jsonToColor"); Color c = Color::rgba(0, 0, 0, 255); - c.setRed(v.getInt(0)); c.setGreen(v.getInt(1)); c.setBlue(v.getInt(2)); @@ -235,9 +234,8 @@ Json jsonFromColor(Color const& color) { result.push_back(color.red()); result.push_back(color.green()); result.push_back(color.blue()); - if (color.alpha() != 255) { + if (color.alpha() < 255) result.push_back(color.alpha()); - } return result; } diff --git a/source/game/StarItemDrop.cpp b/source/game/StarItemDrop.cpp index 1df91f9..f3a74c7 100644 --- a/source/game/StarItemDrop.cpp +++ b/source/game/StarItemDrop.cpp @@ -321,7 +321,7 @@ void ItemDrop::render(RenderCallback* renderCallback) { void ItemDrop::renderLightSources(RenderCallback* renderCallback) { LightSource light; light.pointLight = false; - light.color = Vec3B::filled(20); + light.color = Vec3F::filled(20.f / 255.f); light.position = position(); renderCallback->addLightSource(std::move(light)); } diff --git a/source/game/StarLightSource.hpp b/source/game/StarLightSource.hpp index 8810220..a6948cb 100644 --- a/source/game/StarLightSource.hpp +++ b/source/game/StarLightSource.hpp @@ -7,7 +7,7 @@ namespace Star { struct LightSource { Vec2F position; - Vec3B color; + Vec3F color; bool pointLight; // pointBeam of 0.0 means light has no beam component, as pointBeam goes up, diff --git a/source/game/StarNetworkedAnimator.cpp b/source/game/StarNetworkedAnimator.cpp index f64d293..d013c48 100644 --- a/source/game/StarNetworkedAnimator.cpp +++ b/source/game/StarNetworkedAnimator.cpp @@ -716,7 +716,7 @@ List NetworkedAnimator::lightSources(Vec2F const& translate) const lightSources.append(LightSource{ position + translate, - color.toRgb(), + color.toRgbF(), pair.second.pointLight, pair.second.pointBeam, pointAngle, diff --git a/source/game/StarNpc.cpp b/source/game/StarNpc.cpp index 476dfb4..5450e6f 100644 --- a/source/game/StarNpc.cpp +++ b/source/game/StarNpc.cpp @@ -1014,8 +1014,8 @@ bool Npc::isAdmin() const { return false; } -Vec4B Npc::favoriteColor() const { - return Color::White.toRgba(); +Color Npc::favoriteColor() const { + return Color::White; } float Npc::beamGunRadius() const { diff --git a/source/game/StarNpc.hpp b/source/game/StarNpc.hpp index 083abc6..ab8bc0f 100644 --- a/source/game/StarNpc.hpp +++ b/source/game/StarNpc.hpp @@ -143,7 +143,7 @@ public: Direction facingDirection() const override; Direction walkingDirection() const override; bool isAdmin() const override; - Vec4B favoriteColor() const override; + Color favoriteColor() const override; float beamGunRadius() const override; void addParticles(List const& particles) override; void addSound(String const& sound, float volume = 1.0f, float pitch = 1.0f) override; diff --git a/source/game/StarObject.cpp b/source/game/StarObject.cpp index c56229e..7bac350 100644 --- a/source/game/StarObject.cpp +++ b/source/game/StarObject.cpp @@ -261,7 +261,7 @@ List Object::lightSources() const { LightSource lightSource; lightSource.position = position() + centerOfTile(orientation->lightPosition); - lightSource.color = color.toRgb(); + lightSource.color = color.toRgbF(); lightSource.pointLight = m_config->pointLight; lightSource.pointBeam = m_config->pointBeam; lightSource.beamAngle = orientation->beamAngle; diff --git a/source/game/StarParticleManager.cpp b/source/game/StarParticleManager.cpp index e4e028c..6ba0dbf 100644 --- a/source/game/StarParticleManager.cpp +++ b/source/game/StarParticleManager.cpp @@ -103,11 +103,11 @@ List const& ParticleManager::particles() const { return m_particles; } -List> ParticleManager::lightSources() const { - List> lsources; +List> ParticleManager::lightSources() const { + List> lsources; for (auto const& particle : m_particles) { if (particle.light != Color::Clear) - lsources.append({particle.position, particle.light.toRgb()}); + lsources.append({particle.position, particle.light.toRgbF()}); } return lsources; } diff --git a/source/game/StarParticleManager.hpp b/source/game/StarParticleManager.hpp index 3a94d9f..3371d89 100644 --- a/source/game/StarParticleManager.hpp +++ b/source/game/StarParticleManager.hpp @@ -24,7 +24,7 @@ public: void update(float dt, RectF const& cullRegion, float wind); List const& particles() const; - List> lightSources() const; + List> lightSources() const; private: enum class TileType { Colliding, Water, Empty }; diff --git a/source/game/StarPlayer.cpp b/source/game/StarPlayer.cpp index e9f0daf..7d751af 100644 --- a/source/game/StarPlayer.cpp +++ b/source/game/StarPlayer.cpp @@ -1912,13 +1912,13 @@ bool Player::isAdmin() const { return m_isAdmin; } -void Player::setFavoriteColor(Vec4B color) { - m_identity.color = color; +void Player::setFavoriteColor(Color color) { + m_identity.color = color.toRgba(); updateIdentity(); } -Vec4B Player::favoriteColor() const { - return m_identity.color; +Color Player::favoriteColor() const { + return Color::rgba(m_identity.color); } bool Player::isTeleporting() const { diff --git a/source/game/StarPlayer.hpp b/source/game/StarPlayer.hpp index 933f623..fb32005 100644 --- a/source/game/StarPlayer.hpp +++ b/source/game/StarPlayer.hpp @@ -354,8 +354,8 @@ public: bool isDead() const; void kill(); - void setFavoriteColor(Vec4B color); - Vec4B favoriteColor() const override; + void setFavoriteColor(Color color); + Color favoriteColor() const override; // Starts the teleport animation sequence, locking player movement and // preventing some update code diff --git a/source/game/StarProjectile.cpp b/source/game/StarProjectile.cpp index cfb93ba..5a73e8b 100644 --- a/source/game/StarProjectile.cpp +++ b/source/game/StarProjectile.cpp @@ -379,7 +379,7 @@ void Projectile::renderLightSources(RenderCallback* renderCallback) { if (renderable.is()) renderCallback->addLightSource(renderable.get()); } - renderCallback->addLightSource({ position(), m_config->lightColor, m_config->pointLight, 0.0f, 0.0f, 0.0f }); + renderCallback->addLightSource({position(), m_config->lightColor.toRgbF(), m_config->pointLight, 0.0f, 0.0f, 0.0f}); } Maybe Projectile::receiveMessage(ConnectionId sendingConnection, String const& message, JsonArray const& args) { @@ -824,7 +824,7 @@ void Projectile::processAction(Json const& action) { m_pendingRenderables.append(LightSource{ position(), - jsonToColor(parameters.get("color")).toRgb(), + jsonToColor(parameters.get("color")).toRgbF(), parameters.getBool("pointLight", true), 0.0f, 0.0f, diff --git a/source/game/StarProjectileDatabase.cpp b/source/game/StarProjectileDatabase.cpp index fd52df6..dd7327b 100644 --- a/source/game/StarProjectileDatabase.cpp +++ b/source/game/StarProjectileDatabase.cpp @@ -116,7 +116,7 @@ ProjectileConfigPtr ProjectileDatabase::readConfig(String const& path) { projectileConfig->fullbright = config.getBool("fullbright", false); projectileConfig->renderLayer = parseRenderLayer(config.getString("renderLayer", "Projectile")); - projectileConfig->lightColor = jsonToVec3B(config.get("lightColor", JsonArray{0, 0, 0})); + projectileConfig->lightColor = jsonToColor(config.get("lightColor", JsonArray{0, 0, 0})); projectileConfig->lightPosition = jsonToVec2F(config.get("lightPosition", JsonArray{0, 0})); projectileConfig->pointLight = config.getBool("pointLight", false); diff --git a/source/game/StarProjectileDatabase.hpp b/source/game/StarProjectileDatabase.hpp index f78f150..833d0eb 100644 --- a/source/game/StarProjectileDatabase.hpp +++ b/source/game/StarProjectileDatabase.hpp @@ -66,7 +66,7 @@ struct ProjectileConfig { bool fullbright = false; EntityRenderLayer renderLayer; - Vec3B lightColor; + Color lightColor; Vec2F lightPosition; bool pointLight = false; diff --git a/source/game/StarToolUser.cpp b/source/game/StarToolUser.cpp index 4951128..4135dd0 100644 --- a/source/game/StarToolUser.cpp +++ b/source/game/StarToolUser.cpp @@ -165,7 +165,7 @@ Maybe ToolUser::toolRadius() const { return {}; } -List ToolUser::renderObjectPreviews(Vec2F aimPosition, Direction walkingDirection, bool inToolRange, Vec4B favoriteColor) const { +List ToolUser::renderObjectPreviews(Vec2F aimPosition, Direction walkingDirection, bool inToolRange, Color favoriteColor) const { if (m_suppress.get() || !m_user) return {}; @@ -178,21 +178,16 @@ List ToolUser::renderObjectPreviews(Vec2F aimPosition, Direction walki Color opacityMask = Color::White; opacityMask.setAlphaF(item->getAppropriateOpacity()); - Vec4B favoriteColorTrans; - if (inToolRange && objectDatabase->canPlaceObject(m_user->world(), aimPos, item->objectName())) { - favoriteColorTrans = favoriteColor; - } else { - Color color = Color::rgba(favoriteColor); - color.setHue(color.hue() + 120); - favoriteColorTrans = color.toRgba(); - } + Color favoriteColorTrans = favoriteColor; + if (!inToolRange || !objectDatabase->canPlaceObject(m_user->world(), aimPos, item->objectName())) + favoriteColorTrans.setHue(favoriteColor.hue() + 120); - favoriteColorTrans[3] = m_objectPreviewOuterAlpha * 255; - Color nearWhite = Color::rgba(favoriteColorTrans); + favoriteColorTrans.setAlphaF(m_objectPreviewOuterAlpha); + Color nearWhite = favoriteColorTrans; nearWhite.setValue(1 - (1 - nearWhite.value()) / 5); nearWhite.setSaturation(nearWhite.saturation() / 5); nearWhite.setAlphaF(m_objectPreviewInnerAlpha); - ImageOperation op = BorderImageOperation{m_beamGunGlowBorder, nearWhite.toRgba(), favoriteColorTrans, false, false}; + ImageOperation op = BorderImageOperation{m_beamGunGlowBorder, nearWhite.toRgba(), favoriteColorTrans.toRgba(), false, false}; for (Drawable& drawable : drawables) { if (drawable.isImage()) diff --git a/source/game/StarToolUser.hpp b/source/game/StarToolUser.hpp index de1960c..41fe98f 100644 --- a/source/game/StarToolUser.hpp +++ b/source/game/StarToolUser.hpp @@ -43,7 +43,7 @@ public: // with the rest of everything else, there are TILE previews and OBJECT // previews, but of course one has to go through the render method and the // other has to be rendered separately. - List renderObjectPreviews(Vec2F aimPosition, Direction walkingDirection, bool inToolRange, Vec4B favoriteColor) const; + List renderObjectPreviews(Vec2F aimPosition, Direction walkingDirection, bool inToolRange, Color favoriteColor) const; // Returns the facing override direciton if there is one Maybe setupHumanoidHandItems(Humanoid& humanoid, Vec2F position, Vec2F aimPosition) const; void setupHumanoidHandItemDrawables(Humanoid& humanoid) const; diff --git a/source/game/StarWorldClient.cpp b/source/game/StarWorldClient.cpp index 5e6dc0e..df69b94 100644 --- a/source/game/StarWorldClient.cpp +++ b/source/game/StarWorldClient.cpp @@ -1375,21 +1375,23 @@ void WorldClient::collectLiquid(List const& tilePositions, LiquidId liqui m_outgoingPackets.append(make_shared(tilePositions, liquidId)); } -Maybe WorldClient::waitForLighting(Image* out) { +bool WorldClient::waitForLighting(WorldRenderData* renderData) { MutexLocker prepLocker(m_lightMapPrepMutex); MutexLocker lightMapLocker(m_lightMapMutex); - if (out && !m_lightMap.empty()) { + if (renderData && !m_lightMap.empty()) { for (auto& previewTile : m_previewTiles) { if (previewTile.updateLight) { Vec2I lightArrayPos = m_geometry.diff(previewTile.position, m_lightMinPosition); - if (lightArrayPos[0] >= 0 && lightArrayPos[0] < (int)m_lightMap.width() && lightArrayPos[1] >= 0 && lightArrayPos[1] < (int)m_lightMap.height()) - m_lightMap.set(Vec2U(lightArrayPos), previewTile.light); + if (lightArrayPos[0] >= 0 && lightArrayPos[0] < (int)m_lightMap.width() + && lightArrayPos[1] >= 0 && lightArrayPos[1] < (int)m_lightMap.height()) + m_lightMap.set(lightArrayPos[0], lightArrayPos[1], Color::v3bToFloat(previewTile.light)); } } - *out = std::move(m_lightMap); - return m_lightMinPosition; + renderData->lightMap = std::move(m_lightMap); + renderData->lightMinPosition = m_lightMinPosition; + return true; } - return {}; + return false; } WorldClient::BroadcastCallback& WorldClient::broadcastCallback() { @@ -1636,25 +1638,30 @@ void WorldClient::lightingTileGather() { void WorldClient::lightingCalc() { MutexLocker prepLocker(m_lightMapPrepMutex); + RectI lightRange = m_pendingLightRange; List lights = std::move(m_pendingLights); - List> particleLights = std::move(m_pendingParticleLights); + List> particleLights = std::move(m_pendingParticleLights); m_lightingCalculator.setMonochrome(Root::singleton().configuration()->get("monochromeLighting").toBool()); m_lightingCalculator.begin(lightRange); lightingTileGather(); + prepLocker.unlock(); + + for (auto const& light : lights) { Vec2F position = m_geometry.nearestTo(Vec2F(m_lightingCalculator.calculationRegion().min()), light.position); if (light.pointLight) - m_lightingCalculator.addPointLight(position, Color::v3bToFloat(light.color), light.pointBeam, light.beamAngle, light.beamAmbience); - else - m_lightingCalculator.addSpreadLight(position, Color::v3bToFloat(light.color)); + m_lightingCalculator.addPointLight(position, light.color, light.pointBeam, light.beamAngle, light.beamAmbience); + else { + m_lightingCalculator.addSpreadLight(position, light.color); + } } for (auto const& lightPair : particleLights) { Vec2F position = m_geometry.nearestTo(Vec2F(m_lightingCalculator.calculationRegion().min()), lightPair.first); - m_lightingCalculator.addSpreadLight(position, Color::v3bToFloat(lightPair.second)); + m_lightingCalculator.addSpreadLight(position, lightPair.second); } m_lightingCalculator.calculate(m_pendingLightMap); diff --git a/source/game/StarWorldClient.hpp b/source/game/StarWorldClient.hpp index 6ccd52e..6f40b63 100644 --- a/source/game/StarWorldClient.hpp +++ b/source/game/StarWorldClient.hpp @@ -170,7 +170,7 @@ public: void collectLiquid(List const& tilePositions, LiquidId liquidId); - Maybe waitForLighting(Image* out = nullptr); + bool waitForLighting(WorldRenderData* renderData = nullptr); typedef std::function BroadcastCallback; BroadcastCallback& broadcastCallback(); @@ -278,10 +278,10 @@ private: Mutex m_lightMapPrepMutex; Mutex m_lightMapMutex; - Image m_pendingLightMap; - Image m_lightMap; + Lightmap m_pendingLightMap; + Lightmap m_lightMap; List m_pendingLights; - List> m_pendingParticleLights; + List> m_pendingParticleLights; RectI m_pendingLightRange; Vec2I m_lightMinPosition; List m_previewTiles; diff --git a/source/game/StarWorldImpl.hpp b/source/game/StarWorldImpl.hpp index 828ed1e..dec827f 100644 --- a/source/game/StarWorldImpl.hpp +++ b/source/game/StarWorldImpl.hpp @@ -446,9 +446,9 @@ namespace WorldImpl { for (auto const& light : entity->lightSources()) { Vec2F position = worldGeometry.nearestTo(Vec2F(lighting.calculationRegion().min()), light.position); if (light.pointLight) - lighting.addPointLight(position, Color::v3bToFloat(light.color).sum() / 3.0f, light.pointBeam, light.beamAngle, light.beamAmbience); + lighting.addPointLight(position, light.color.sum() / 3.0f, light.pointBeam, light.beamAngle, light.beamAmbience); else - lighting.addSpreadLight(position, Color::v3bToFloat(light.color).sum() / 3.0f); + lighting.addSpreadLight(position, light.color.sum() / 3.0f); } } diff --git a/source/game/StarWorldRenderData.hpp b/source/game/StarWorldRenderData.hpp index b392615..0030a10 100644 --- a/source/game/StarWorldRenderData.hpp +++ b/source/game/StarWorldRenderData.hpp @@ -9,6 +9,7 @@ #include "StarWeatherTypes.hpp" #include "StarEntity.hpp" #include "StarThread.hpp" +#include "StarCellularLighting.hpp" namespace Star { @@ -17,6 +18,7 @@ struct EntityDrawables { Map> layers; }; + struct WorldRenderData { void clear(); @@ -25,7 +27,7 @@ struct WorldRenderData { Vec2I tileMinPosition; RenderTileArray tiles; Vec2I lightMinPosition; - Image lightMap; + Lightmap lightMap; List entityDrawables; List const* particles; diff --git a/source/game/interfaces/StarBeamItem.cpp b/source/game/interfaces/StarBeamItem.cpp index 2bf8e02..030a29c 100644 --- a/source/game/interfaces/StarBeamItem.cpp +++ b/source/game/interfaces/StarBeamItem.cpp @@ -38,7 +38,7 @@ BeamItem::BeamItem(Json config) { m_innerBrightnessScale = config.get("innerBrightnessScale").toFloat(); m_firstStripeThickness = config.get("firstStripeThickness").toFloat(); m_secondStripeThickness = config.get("secondStripeThickness").toFloat(); - m_color = {255, 255, 255, 255}; + m_color = Color::White; m_particleGenerateCooldown = .25; m_inRangeLastUpdate = false; } @@ -160,12 +160,9 @@ List BeamItem::beamDrawables(bool canPlace) const { if ((endPoint - owner()->position()).magnitude() <= m_range && curveLen <= m_range) { m_inRangeLastUpdate = true; int numLines = projectOntoRange(m_minBeamLines, m_maxBeamLines); - Vec4B mainColor = m_color; - if (!canPlace) { - Color temp = Color::rgba(m_color); - temp.setHue(temp.hue() + 120); - mainColor = temp.toRgba(); - } + Color mainColor = m_color; + if (!canPlace) + mainColor.setHue(mainColor.hue() + 120); m_lastUpdateColor = mainColor; String endImage = ""; @@ -189,9 +186,9 @@ List BeamItem::beamDrawables(bool canPlace) const { for (auto line = 0; line < numLines; line++) { float lineThickness = rangeRand(m_beamWidthDev, m_minBeamWidth, m_maxBeamWidth); float beamTransparency = rangeRand(m_beamTransDev, m_minBeamTrans, m_maxBeamTrans); - mainColor[3] = mainColor[3] * beamTransparency; + mainColor.setAlphaF(mainColor.alphaF() * beamTransparency); Vec2F previousLoc = m_beamCurve.origin(); // lines meet at origin and dest. - Color innerStripe = Color::rgba(mainColor); + Color innerStripe = mainColor; innerStripe.setValue(1 - (1 - innerStripe.value()) / m_innerBrightnessScale); innerStripe.setSaturation(innerStripe.saturation() / m_innerBrightnessScale); Vec4B firstStripe = innerStripe.toRgba(); @@ -206,7 +203,7 @@ List BeamItem::beamDrawables(bool canPlace) const { m_beamCurve.pointAt(pos) + Vec2F(rangeRand(m_beamJitterDev, -m_maxBeamJitter, m_maxBeamJitter), rangeRand(m_beamJitterDev, -m_maxBeamJitter, m_maxBeamJitter)); res.push_back( - Drawable::makeLine(Line2F(previousLoc, currentLoc), lineThickness, Color::rgba(mainColor), Vec2F())); + Drawable::makeLine(Line2F(previousLoc, currentLoc), lineThickness, mainColor, Vec2F())); res.push_back(Drawable::makeLine(Line2F(previousLoc, currentLoc), lineThickness * m_firstStripeThickness, Color::rgba(firstStripe), @@ -218,7 +215,7 @@ List BeamItem::beamDrawables(bool canPlace) const { previousLoc = std::move(currentLoc); } res.push_back(Drawable::makeLine( - Line2F(previousLoc, m_beamCurve.dest()), lineThickness, Color::rgba(mainColor), Vec2F())); + Line2F(previousLoc, m_beamCurve.dest()), lineThickness, mainColor, Vec2F())); res.push_back(Drawable::makeLine(Line2F(previousLoc, m_beamCurve.dest()), lineThickness * m_firstStripeThickness, Color::rgba(firstStripe), @@ -243,7 +240,7 @@ List BeamItem::beamDrawables(bool canPlace) const { beamParticle.position = m_beamCurve.pointAt(curveLoc); beamParticle.size = 1.0f; - Color randomColor = Color::rgba(m_lastUpdateColor); + Color randomColor = m_lastUpdateColor; randomColor.setValue(1 - (1 - randomColor.value()) / Random::randf(1, 4)); randomColor.setSaturation(randomColor.saturation() / Random::randf(1, 4)); diff --git a/source/game/interfaces/StarBeamItem.hpp b/source/game/interfaces/StarBeamItem.hpp index 8267a40..e8f471d 100644 --- a/source/game/interfaces/StarBeamItem.hpp +++ b/source/game/interfaces/StarBeamItem.hpp @@ -63,10 +63,10 @@ protected: float m_innerBrightnessScale; float m_firstStripeThickness; float m_secondStripeThickness; - Vec4B m_color; + Color m_color; mutable bool m_inRangeLastUpdate; - mutable Vec4B m_lastUpdateColor; + mutable Color m_lastUpdateColor; mutable float m_particleGenerateCooldown; CSplineF m_beamCurve; diff --git a/source/game/interfaces/StarToolUserEntity.hpp b/source/game/interfaces/StarToolUserEntity.hpp index dcda91d..2d7cc49 100644 --- a/source/game/interfaces/StarToolUserEntity.hpp +++ b/source/game/interfaces/StarToolUserEntity.hpp @@ -26,7 +26,7 @@ public: virtual Vec2F aimPosition() const = 0; virtual bool isAdmin() const = 0; - virtual Vec4B favoriteColor() const = 0; + virtual Color favoriteColor() const = 0; virtual String species() const = 0; virtual void requestEmote(String const& emote) = 0; diff --git a/source/game/items/StarInspectionTool.cpp b/source/game/items/StarInspectionTool.cpp index 6ab6789..a9e3109 100644 --- a/source/game/items/StarInspectionTool.cpp +++ b/source/game/items/StarInspectionTool.cpp @@ -56,7 +56,7 @@ List InspectionTool::lightSources() const { LightSource lightSource; lightSource.pointLight = true; lightSource.position = owner()->position() + owner()->handPosition(hand(), m_lightPosition - m_handPosition); - lightSource.color = m_lightColor.toRgb(); + lightSource.color = m_lightColor.toRgbF(); lightSource.pointBeam = m_beamWidth; lightSource.beamAngle = angle; lightSource.beamAmbience = m_ambientFactor; diff --git a/source/game/items/StarMaterialItem.cpp b/source/game/items/StarMaterialItem.cpp index 42f90fc..4d13a62 100644 --- a/source/game/items/StarMaterialItem.cpp +++ b/source/game/items/StarMaterialItem.cpp @@ -122,7 +122,7 @@ void MaterialItem::update(float dt, FireMode fireMode, bool shifting, HashSetfavoriteColor()).mix(Color::White); + Color color = owner()->favoriteColor().mix(Color::White); color.setAlphaF(color.alphaF() * pulseLevel * 0.95f); auto addIndicator = [&](String const& path) { Vec2F basePosition = Vec2F(0.5f, 0.5f); @@ -336,7 +336,7 @@ TileCollisionOverride& MaterialItem::collisionOverride() { List MaterialItem::previewTiles(bool shifting) const { List result; if (initialized()) { - Color lightColor = Color::rgba(owner()->favoriteColor()); + Color lightColor = owner()->favoriteColor(); Vec3B light = lightColor.toRgb(); auto material = materialId(); diff --git a/source/game/items/StarTools.cpp b/source/game/items/StarTools.cpp index e1bb576..0f2781a 100644 --- a/source/game/items/StarTools.cpp +++ b/source/game/items/StarTools.cpp @@ -241,7 +241,7 @@ List Flashlight::lightSources() const { LightSource lightSource; lightSource.pointLight = true; lightSource.position = owner()->position() + owner()->handPosition(hand(), (m_lightPosition - m_handPosition) / TilePixels); - lightSource.color = m_lightColor.toRgb(); + lightSource.color = m_lightColor.toRgbF(); lightSource.pointBeam = m_beamWidth; lightSource.beamAngle = angle; lightSource.beamAmbience = m_ambientFactor; @@ -367,7 +367,7 @@ List BeamMiningTool::previewTiles(bool shifting) const { if (ownerp && worldp) { if (ownerp->isAdmin() || ownerp->inToolRange()) { - Color lightColor = Color::rgba(ownerp->favoriteColor()); + Color lightColor = ownerp->favoriteColor(); if (!ready()) lightColor *= Color::rgbaf(0.75f, 0.75f, 0.75f, 1.0f); Vec3B light = lightColor.toRgb(); @@ -604,13 +604,13 @@ PaintingBeamTool::PaintingBeamTool(Json const& config, String const& directory, m_blockVolume = assets->json("/sfx.config:miningBlockVolume").toFloat(); m_endType = EndType::Object; - for (auto color : instanceValue("colorNumbers").toArray()) + for (auto& color : instanceValue("colorNumbers").toArray()) m_colors.append(jsonToColor(color)); m_colorKeys = jsonToStringList(instanceValue("colorKeys")); m_colorIndex = instanceValue("colorIndex", 0).toInt(); - m_color = m_colors[m_colorIndex].toRgba(); + m_color = m_colors[m_colorIndex]; } ItemPtr PaintingBeamTool::clone() const { @@ -666,7 +666,7 @@ List PaintingBeamTool::previewTiles(bool shifting) const { void PaintingBeamTool::init(ToolUserEntity* owner, ToolHand hand) { FireableItem::init(owner, hand); BeamItem::init(owner, hand); - m_color = m_colors[m_colorIndex].toRgba(); + m_color = m_colors[m_colorIndex]; } List PaintingBeamTool::nonRotatedDrawables() const { @@ -681,7 +681,7 @@ void PaintingBeamTool::fire(FireMode mode, bool shifting, bool edgeTriggered) { if (mode == FireMode::Alt && edgeTriggered) { m_colorIndex = (m_colorIndex + 1) % m_colors.size(); - m_color = m_colors[m_colorIndex].toRgba(); + m_color = m_colors[m_colorIndex]; setInstanceValue("colorIndex", m_colorIndex); return; } diff --git a/source/game/scripting/StarLuaAnimationComponent.hpp b/source/game/scripting/StarLuaAnimationComponent.hpp index 540f517..8866724 100644 --- a/source/game/scripting/StarLuaAnimationComponent.hpp +++ b/source/game/scripting/StarLuaAnimationComponent.hpp @@ -76,7 +76,7 @@ LuaAnimationComponent::LuaAnimationComponent() { animationCallbacks.registerCallback("addLightSource", [this](LuaTable const& lightSourceTable) { m_lightSources.append({ lightSourceTable.get("position"), - lightSourceTable.get("color").toRgb(), + lightSourceTable.get("color").toRgbF(), lightSourceTable.get>("pointLight").value(), lightSourceTable.get>("pointBeam").value(), lightSourceTable.get>("beamAngle").value(), diff --git a/source/rendering/StarTilePainter.cpp b/source/rendering/StarTilePainter.cpp index 31fe0a6..58d9d28 100644 --- a/source/rendering/StarTilePainter.cpp +++ b/source/rendering/StarTilePainter.cpp @@ -38,20 +38,19 @@ TilePainter::TilePainter(RendererPtr renderer) : TileDrawer() { void TilePainter::adjustLighting(WorldRenderData& renderData) const { RectI lightRange = RectI::withSize(renderData.lightMinPosition, Vec2I(renderData.lightMap.size())); forEachRenderTile(renderData, lightRange, [&](Vec2I const& pos, RenderTile const& tile) { - // Only adjust lighting for full tiles + // Only adjust lighting for tiles with liquid above the draw threshold float drawLevel = liquidDrawLevel(byteToFloat(tile.liquidLevel)); if (drawLevel == 0.0f) return; auto lightIndex = Vec2U(pos - renderData.lightMinPosition); - auto lightValue = renderData.lightMap.get(lightIndex).vec3(); + auto lightValue = renderData.lightMap.get(lightIndex.x(), lightIndex.y()); auto const& liquid = m_liquids[tile.liquidId]; - Vec3F tileLight = Vec3F(lightValue); - float darknessLevel = (1 - tileLight.sum() / (3.0f * 255.0f)) * drawLevel; - lightValue = Vec3B(tileLight.piecewiseMultiply(Vec3F::filled(1 - darknessLevel) + liquid.bottomLightMix * darknessLevel)); + float darknessLevel = (1.f - (lightValue.sum() / 3.0f)) * drawLevel; + lightValue = lightValue.piecewiseMultiply(Vec3F::filled(1.f - darknessLevel) + liquid.bottomLightMix * darknessLevel); - renderData.lightMap.set(lightIndex, lightValue); + renderData.lightMap.set(lightIndex.x(), lightIndex.y(), lightValue); }); } diff --git a/source/rendering/StarWorldPainter.cpp b/source/rendering/StarWorldPainter.cpp index c55f372..5d33182 100644 --- a/source/rendering/StarWorldPainter.cpp +++ b/source/rendering/StarWorldPainter.cpp @@ -83,6 +83,7 @@ void WorldPainter::render(WorldRenderData& renderData, function lightWai LogMap::set("client_render_world_async_light_wait", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - start)); } + m_renderer->setEffectParameter("lightMapEnabled", !renderData.isFullbright); if (renderData.isFullbright) { m_renderer->setEffectTexture("lightMap", Image::filled(Vec2U(1, 1), { 255, 255, 255, 255 }, PixelFormat::RGB24)); m_renderer->setEffectParameter("lightMapMultiplier", 1.0f);