Merge branch 'lighting'

This commit is contained in:
Kae 2024-03-21 16:19:39 +11:00
commit 9b10964a3e
50 changed files with 628 additions and 254 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@
/mac/
/dist/
/installer/
/vcpkg/
/client_distribution/
/server_distribution/
enc_temp_folder/

View File

@ -0,0 +1 @@
{}

View File

@ -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

View File

@ -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;
}

View File

@ -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
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

View File

@ -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)

View File

@ -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

View File

@ -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<GlFrameBuffer>(pair.second);
setScreenSize(m_screenSize);
}
void OpenGl20Renderer::loadEffectConfig(String const& name, Json const& effectConfig, StringMap<String> 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<GLenum> 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<GlLoneTexture> {
auto OpenGl20Renderer::createGlTexture(ImageView const& image, TextureAddressing addressing, TextureFiltering filtering)
->RefPtr<GlLoneTexture> {
auto glLoneTexture = make_ref<GlLoneTexture>();
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;
}

View File

@ -25,7 +25,7 @@ public:
void loadEffectConfig(String const& name, Json const& effectConfig, StringMap<String> 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<RectI> 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<GlLoneTexture> createGlTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering);
static RefPtr<GlLoneTexture> createGlTexture(ImageView const& image, TextureAddressing addressing, TextureFiltering filtering);
shared_ptr<GlRenderBuffer> createGlRenderBuffer();

View File

@ -25,6 +25,7 @@ SET (star_base_HEADERS
SET (star_base_SOURCES
StarAnimatedPartSet.cpp
StarAssets.cpp
StarCellularLightArray.cpp
StarCellularLighting.cpp
StarConfiguration.cpp
StarDirectoryAssetSource.cpp

View File

@ -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<StringSet, String>("byExtension", bind(&Assets::scanExtension, this, _1));
callbacks.registerCallbackWithSignature<Json, String>("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<String>(data))
bytes = ByteArray(str->utf8Ptr(), str->utf8Size());
else {
auto json = engine.luaTo<Json>(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<String>(data))
bytes = ByteArray(str->utf8Ptr(), str->utf8Size());
else {
auto json = engine.luaTo<Json>(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<LuaContext>(as<LuaEngine>(m_luaEngine.get())->createContext());
context->load(patchStream, patchPath);
}
auto newResult = context->invokePath<Json>("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) {

View File

@ -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<String> m_bestFramesFiles;
mutable StringMap<FramesSpecificationConstPtr> m_framesSpecifications;
// Lua
RefPtr<RefCounter> m_luaEngine; // dumb but to avoid including Lua.hpp in here...
mutable StringMap<LuaContextPtr> m_patchContexts;
mutable Mutex m_luaMutex;
// Paths of all used asset sources, in load order.
StringList m_assetSources;

View File

@ -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<ScalarLightTraits>::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<float>(xmin, light.position[0] - maxRange));
size_t lymin = std::floor(std::max<float>(ymin, light.position[1] - maxRange));
size_t lxmax = std::ceil(std::min<float>(xmax, light.position[0] + maxRange));
size_t lymax = std::ceil(std::min<float>(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<ColoredLightTraits>::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<float>(xmin, light.position[0] - maxRange));
size_t lymin = std::floor(std::max<float>(ymin, light.position[1] - maxRange));
size_t lxmax = std::ceil(std::min<float>(xmax, light.position[0] + maxRange));
size_t lymax = std::ceil(std::min<float>(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));
}
}
}
}
}

View File

@ -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<LightTraits>::calculateLightSpread(size_t xMin, size_t y
}
}
template <typename LightTraits>
void CellularLightArray<LightTraits>::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<float>(xmin, light.position[0] - maxRange));
size_t lymin = std::floor(std::max<float>(ymin, light.position[1] - maxRange));
size_t lxmax = std::ceil(std::min<float>(xmax, light.position[0] + maxRange));
size_t lymax = std::ceil(std::min<float>(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 <typename LightTraits>
float CellularLightArray<LightTraits>::lineAttenuation(Vec2F const& start, Vec2F const& end,
float perObstacleAttenuation, float maxAttenuation) {

View File

@ -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<float[]>(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<float[]>(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());

View File

@ -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<float[]> 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:

View File

@ -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<String> 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<CallbackListener>([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<String> 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;

View File

@ -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<void> m_rootLoader;
CallbackListenerPtr m_reloadListener;
MainAppState m_state = MainAppState::Startup;
// Valid after applicationInit is called

View File

@ -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();
}
}

View File

@ -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;
};
}

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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,

View File

@ -716,7 +716,7 @@ List<LightSource> NetworkedAnimator::lightSources(Vec2F const& translate) const
lightSources.append(LightSource{
position + translate,
color.toRgb(),
color.toRgbF(),
pair.second.pointLight,
pair.second.pointBeam,
pointAngle,

View File

@ -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 {

View File

@ -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<Particle> const& particles) override;
void addSound(String const& sound, float volume = 1.0f, float pitch = 1.0f) override;

View File

@ -261,7 +261,7 @@ List<LightSource> 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;

View File

@ -103,11 +103,11 @@ List<Particle> const& ParticleManager::particles() const {
return m_particles;
}
List<pair<Vec2F, Vec3B>> ParticleManager::lightSources() const {
List<pair<Vec2F, Vec3B>> lsources;
List<pair<Vec2F, Vec3F>> ParticleManager::lightSources() const {
List<pair<Vec2F, Vec3F>> 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;
}

View File

@ -24,7 +24,7 @@ public:
void update(float dt, RectF const& cullRegion, float wind);
List<Particle> const& particles() const;
List<pair<Vec2F, Vec3B>> lightSources() const;
List<pair<Vec2F, Vec3F>> lightSources() const;
private:
enum class TileType { Colliding, Water, Empty };

View File

@ -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 {

View File

@ -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

View File

@ -379,7 +379,7 @@ void Projectile::renderLightSources(RenderCallback* renderCallback) {
if (renderable.is<LightSource>())
renderCallback->addLightSource(renderable.get<LightSource>());
}
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<Json> 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,

View File

@ -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);

View File

@ -66,7 +66,7 @@ struct ProjectileConfig {
bool fullbright = false;
EntityRenderLayer renderLayer;
Vec3B lightColor;
Color lightColor;
Vec2F lightPosition;
bool pointLight = false;

View File

@ -165,7 +165,7 @@ Maybe<float> ToolUser::toolRadius() const {
return {};
}
List<Drawable> ToolUser::renderObjectPreviews(Vec2F aimPosition, Direction walkingDirection, bool inToolRange, Vec4B favoriteColor) const {
List<Drawable> ToolUser::renderObjectPreviews(Vec2F aimPosition, Direction walkingDirection, bool inToolRange, Color favoriteColor) const {
if (m_suppress.get() || !m_user)
return {};
@ -178,21 +178,16 @@ List<Drawable> 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())

View File

@ -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<Drawable> renderObjectPreviews(Vec2F aimPosition, Direction walkingDirection, bool inToolRange, Vec4B favoriteColor) const;
List<Drawable> renderObjectPreviews(Vec2F aimPosition, Direction walkingDirection, bool inToolRange, Color favoriteColor) const;
// Returns the facing override direciton if there is one
Maybe<Direction> setupHumanoidHandItems(Humanoid& humanoid, Vec2F position, Vec2F aimPosition) const;
void setupHumanoidHandItemDrawables(Humanoid& humanoid) const;

View File

@ -1375,21 +1375,23 @@ void WorldClient::collectLiquid(List<Vec2I> const& tilePositions, LiquidId liqui
m_outgoingPackets.append(make_shared<CollectLiquidPacket>(tilePositions, liquidId));
}
Maybe<Vec2I> 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<LightSource> lights = std::move(m_pendingLights);
List<std::pair<Vec2F, Vec3B>> particleLights = std::move(m_pendingParticleLights);
List<std::pair<Vec2F, Vec3F>> 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);

View File

@ -170,7 +170,7 @@ public:
void collectLiquid(List<Vec2I> const& tilePositions, LiquidId liquidId);
Maybe<Vec2I> waitForLighting(Image* out = nullptr);
bool waitForLighting(WorldRenderData* renderData = nullptr);
typedef std::function<bool(PlayerPtr, StringView)> 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<LightSource> m_pendingLights;
List<std::pair<Vec2F, Vec3B>> m_pendingParticleLights;
List<std::pair<Vec2F, Vec3F>> m_pendingParticleLights;
RectI m_pendingLightRange;
Vec2I m_lightMinPosition;
List<PreviewTile> m_previewTiles;

View File

@ -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);
}
}

View File

@ -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<EntityRenderLayer, List<Drawable>> layers;
};
struct WorldRenderData {
void clear();
@ -25,7 +27,7 @@ struct WorldRenderData {
Vec2I tileMinPosition;
RenderTileArray tiles;
Vec2I lightMinPosition;
Image lightMap;
Lightmap lightMap;
List<EntityDrawables> entityDrawables;
List<Particle> const* particles;

View File

@ -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<Drawable> 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<Drawable> 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<Drawable> 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<Drawable> 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<Drawable> 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));

View File

@ -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;

View File

@ -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;

View File

@ -56,7 +56,7 @@ List<LightSource> 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;

View File

@ -122,7 +122,7 @@ void MaterialItem::update(float dt, FireMode fireMode, bool shifting, HashSet<Mo
void MaterialItem::render(RenderCallback* renderCallback, EntityRenderLayer) {
if (m_collisionOverride != TileCollisionOverride::None) {
float pulseLevel = 1.f - 0.3f * 0.5f * ((float)sin(2 * Constants::pi * 4.0 * Time::monotonicTime()) + 1.f);
Color color = Color::rgba(owner()->favoriteColor()).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<PreviewTile> MaterialItem::previewTiles(bool shifting) const {
List<PreviewTile> result;
if (initialized()) {
Color lightColor = Color::rgba(owner()->favoriteColor());
Color lightColor = owner()->favoriteColor();
Vec3B light = lightColor.toRgb();
auto material = materialId();

View File

@ -241,7 +241,7 @@ List<LightSource> 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<PreviewTile> 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<PreviewTile> 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<Drawable> 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;
}

View File

@ -76,7 +76,7 @@ LuaAnimationComponent<Base>::LuaAnimationComponent() {
animationCallbacks.registerCallback("addLightSource", [this](LuaTable const& lightSourceTable) {
m_lightSources.append({
lightSourceTable.get<Vec2F>("position"),
lightSourceTable.get<Color>("color").toRgb(),
lightSourceTable.get<Color>("color").toRgbF(),
lightSourceTable.get<Maybe<bool>>("pointLight").value(),
lightSourceTable.get<Maybe<float>>("pointBeam").value(),
lightSourceTable.get<Maybe<float>>("beamAngle").value(),

View File

@ -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);
});
}

View File

@ -83,6 +83,7 @@ void WorldPainter::render(WorldRenderData& renderData, function<bool()> 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);