diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index 0f508dc6a..16ca5f7a0 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -171,6 +171,7 @@ void main(void) // Generate waves with Perlin-type noise. // The constants are calibrated such that they roughly // correspond to the old sine waves. + // Note: The same thing is implemented in clientmap.cpp. Keep them consistent! vec3 wavePos = (mWorld * pos).xyz + cameraOffset; // The waves are slightly compressed along the z-axis to get // wave-fronts along the x-axis. diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl index db72d7f7d..8daa149c4 100644 --- a/client/shaders/object_shader/opengl_fragment.glsl +++ b/client/shaders/object_shader/opengl_fragment.glsl @@ -5,6 +5,9 @@ uniform lowp vec4 fogColor; uniform float fogDistance; uniform float fogShadingParameter; +// Only used for the wieldhand. 0 otherwise. +uniform vec4 wield_posteffect_color; + // The cameraOffset is the current center of the visible world. uniform highp vec3 cameraOffset; uniform float animationTimer; @@ -458,5 +461,12 @@ void main(void) col = mix(fogColor * pow(fogColor / fogColorMax, vec4(2.0 * clarity)), col, clarity); col = vec4(col.rgb, base.a); + // Apply posteffect color for the wieldhand, by blending it above this + // fragment. + // The alpha channel is not blended. The posteffect geometry behind the + // wieldhand already makes the image less transparent. + // wield_posteffect_color.rgb is premultiplied. + col.rgb = col.rgb * (1.0 - wield_posteffect_color.a) + wield_posteffect_color.rgb; + gl_FragData[0] = col; } diff --git a/src/client/camera.cpp b/src/client/camera.cpp index 4abe062d7..8d8e4e5af 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -600,7 +600,8 @@ void Camera::wield(const ItemStack &item) } } -void Camera::drawWieldedTool(irr::core::matrix4* translation) +void Camera::drawWieldedTool(irr::core::matrix4* translation, + const video::SColorf &post_color) { // Clear Z buffer so that the wielded tool stays in front of world geometry m_wieldmgr->getVideoDriver()->clearBuffers(video::ECBF_DEPTH); @@ -624,7 +625,9 @@ void Camera::drawWieldedTool(irr::core::matrix4* translation) cam->updateAbsolutePosition(); cam->setTarget(focusPoint); } + m_client->setWieldPostEffectColor(post_color); m_wieldmgr->drawAll(); + m_client->setWieldPostEffectColor(video::SColorf(0.0f,0.0f,0.0f,0.0f)); } void Camera::drawNametags() diff --git a/src/client/camera.h b/src/client/camera.h index 3715bebad..30915c767 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -160,7 +160,8 @@ public: // Draw the wielded tool. // This has to happen *after* the main scene is drawn. // Warning: This clears the Z buffer. - void drawWieldedTool(irr::core::matrix4* translation=NULL); + void drawWieldedTool(irr::core::matrix4* translation = nullptr, + const video::SColorf &post_color = video::SColorf(0.0f,0.0f,0.0f,0.0f)); // Toggle the current camera mode void toggleCameraMode() { diff --git a/src/client/client.h b/src/client/client.h index e4bb77ab2..bb2d7f11b 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -430,6 +430,16 @@ public: return m_mesh_grid; } + video::SColorf getWieldPostEffectColor() const + { + return m_wield_posteffect_color; + } + + void setWieldPostEffectColor(video::SColorf color) + { + m_wield_posteffect_color = color; + } + bool inhibit_inventory_revert = false; private: @@ -592,4 +602,6 @@ private: // The number of blocks the client will combine for mesh generation. MeshGrid m_mesh_grid; + + video::SColorf m_wield_posteffect_color{0}; }; diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index ee05d8816..019f8f7e7 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -5,6 +5,7 @@ #include "clientmap.h" #include "client.h" #include "client/mesh.h" +#include "content_mapblock.h" #include "mapblock_mesh.h" #include #include @@ -16,9 +17,9 @@ #include "settings.h" #include "camera.h" // CameraModes #include "util/basic_macros.h" +#include "util/convex_polygon.h" #include "util/tracy_wrapper.h" #include "client/renderingengine.h" - #include namespace { @@ -1206,39 +1207,312 @@ int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor, return ret; } -void ClientMap::renderPostFx(CameraMode cam_mode) +std::vector> +ClientMap::getPostFxPolygons() { - // Sadly ISceneManager has no "post effects" render pass, in that case we - // could just register for that and handle it in renderMap(). + using mat4 = core::matrix4; + using v4f = std::array; - MapNode n = getNode(floatToInt(m_camera_position, BS)); + auto vector_cross_4d = [](const v4f &v1, const v4f &v2, const v4f &v3) -> v4f + { + using varr3 = std::array; + using mat33 = std::array; - const ContentFeatures& features = m_nodedef->get(n); - video::SColor post_color = features.post_effect_color; - - if (features.post_effect_color_shaded) { - auto apply_light = [] (u32 color, u32 light) { - return core::clamp(core::round32(color * light / 255.0f), 0, 255); + auto det_3d = [](const mat33 &m) { + return m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) + - m[0][1] * (m[1][0] * m[2][2] - m[2][0] * m[1][2]) + + m[0][2] * (m[1][0] * m[2][1] - m[2][0] * m[1][1]); }; - post_color.setRed(apply_light(post_color.getRed(), m_camera_light_color.getRed())); - post_color.setGreen(apply_light(post_color.getGreen(), m_camera_light_color.getGreen())); - post_color.setBlue(apply_light(post_color.getBlue(), m_camera_light_color.getBlue())); + + auto index33 = [&](int i1, int i2, int i3) -> mat33 { + return { + varr3{v1[i1], v1[i2], v1[i3]}, + varr3{v2[i1], v2[i2], v2[i3]}, + varr3{v3[i1], v3[i2], v3[i3]} + }; + }; + + return v4f{ + +det_3d(index33( 1, 2, 3)), + -det_3d(index33(0, 2, 3)), + +det_3d(index33(0, 1, 3)), + -det_3d(index33(0, 1, 2 )), + }; + }; + + auto mat4_mult_v4f = [](const mat4 &m, const v4f &v) { + v4f ret; + m.transformVec4(ret.data(), v.data()); + return ret; + }; + + auto get_post_color = [&](const ContentFeatures &features) { + video::SColor post_color = features.post_effect_color; + + if (features.post_effect_color_shaded) { + auto apply_light = [](u32 color, u32 light) { + return core::clamp(core::round32(color * light / 255.0f), 0, 255); + }; + post_color.setRed(apply_light(post_color.getRed(), m_camera_light_color.getRed())); + post_color.setGreen(apply_light(post_color.getGreen(), m_camera_light_color.getGreen())); + post_color.setBlue(apply_light(post_color.getBlue(), m_camera_light_color.getBlue())); + } + + // Solid nodes are full of darkness. + if (features.solidness == 2 && !m_control.allow_noclip) + post_color = video::SColor(255, 0, 0, 0); + + return post_color; + }; + + const std::optional liquid_wave_params = + g_settings->getBool("enable_waving_water") ? + LiquidWaveParams{ + g_settings->getFloat("water_wave_height"), + g_settings->getFloat("water_wave_length"), + g_settings->getFloat("water_wave_speed"), + (float)(m_client->getEnv().getFrameTime() % 1000000) / 100000.f, + } + : std::optional(std::nullopt); + + const bool smooth_lighting = g_settings->getBool("smooth_lighting"); + + // transposed inversed view-(quasi)projection matrix + // This matrix moves the near plane from camera-offset-local world-space to + // clip-space. + // (It's the transposed inversed because we want to transform planes.) + // (Only the near plane is transformed correctly (we don't care about the + // rest of the space), therefore "(quasi)projection".) + // (We also don't care for positive scalar multiples, so the adjugate would + // work too.) + const mat4 vp_mat_ti = [&]() { + auto camera_node = m_client->getCamera()->getCameraNode(); + + const mat4 view_mat = camera_node->getViewMatrix(); + + // The projection matrix moves the near-left-up corner of the frustum + // to (-1,1,-1), and near-right-down to (1,-1,-1). But we need (0,0,0) + // and (1,1,0). coord_transf moves the near plane accordingly. + // (We only care about the near plane, so this scale + translate is ok.) + const mat4 coord_transf = mat4( + 0.5f, 0.0f, 0.0f, 0.5f, + 0.0f, -0.5f, 0.0f, 0.5f, + 0.0f, 0.0f, 1.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ).getTransposed(); + const mat4 proj_mat = coord_transf * camera_node->getProjectionMatrix(); + + return mat4(proj_mat * view_mat, mat4::EM4CONST_INVERSE_TRANSPOSED); + }(); + + std::vector> polygons; + + // Go through all 8 nodes that the near plane possibly instersects with + + // the one in the (-1,-1,-1) corner + const v3s16 minus_corner( + std::floor(m_camera_position.X/BS), + std::floor(m_camera_position.Y/BS), + std::floor(m_camera_position.Z/BS) + ); + + for (const v3s16 &corner_dir : { + v3s16(0, 0, 0), + v3s16(1, 0, 0), + v3s16(0, 1, 0), + v3s16(1, 1, 0), + v3s16(0, 0, 1), + v3s16(1, 0, 1), + v3s16(0, 1, 1), + v3s16(1, 1, 1), + }) { + const v3s16 npos = minus_corner + corner_dir; + + const MapNode node = getNode(npos); + + const ContentFeatures &features = m_nodedef->get(node); + const video::SColor post_color = get_post_color(features); + + if (post_color.getAlpha() == 0) + continue; + + const ConvexPolygon screen_polygon{{ + {0.0f, 0.0f}, // upper-left + {1.0f, 0.0f}, // upper-right + {1.0f, 1.0f}, // lower-right + {0.0f, 1.0f}, // lower-left + }}; + + // the (inverse and inverse transposed) world matrix for the node + // Transforms planes from node-local space to camera-offset-local world + // BS space. + mat4 world_mat_i = mat4::EM4CONST_IDENTITY; + world_mat_i.setTranslation(intToFloat(-npos + m_camera_offset, 1.0f)); + world_mat_i.setScale(1.0f/BS); + const mat4 world_mat_ti(world_mat_i, mat4::EM4CONST_TRANSPOSED); + + const mat4 wvp_mat_ti = vp_mat_ti * world_mat_ti; + + auto clip_polygon_by_objsp_plane = [&](ConvexPolygon &polyg, + const v4f &clip_plane) -> bool { + v4f clip_plane_clipspace = mat4_mult_v4f(wvp_mat_ti, clip_plane); + + // The clip plane (and all points p in the clip plane) are at z=0. + // So the c component in the following inequation drops away: + // a*p.X + b*p.Y + c*p.Z + d >= 0 + v3f clip_line(clip_plane_clipspace[0], clip_plane_clipspace[1], clip_plane_clipspace[3]); + + polyg = polyg.clip(clip_line); + return polyg.vertices.size() >= 3; + }; + + auto clip_polygon_by_objsp_planes = [&](ConvexPolygon &polyg, + const std::vector &clip_planes) -> bool { + for (const v4f &clip_plane : clip_planes) + if (!clip_polygon_by_objsp_plane(polyg, clip_plane)) + return false; + return true; + }; + + if (features.drawtype == NDT_NORMAL) { + // draw full cube + ConvexPolygon polygon = screen_polygon; + if (!clip_polygon_by_objsp_planes(polygon, { + { 2.0f, 0.0f, 0.0f, 1.0f}, + {-2.0f, 0.0f, 0.0f, 1.0f}, + {0.0f, 2.0f, 0.0f, 1.0f}, + {0.0f, -2.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 2.0f, 1.0f}, + {0.0f, 0.0f, -2.0f, 1.0f}, + })) + continue; + polygons.emplace_back(std::move(polygon), post_color); + + } else if (features.drawtype == NDT_LIQUID + || features.drawtype == NDT_FLOWINGLIQUID) { + ConvexPolygon polygon = screen_polygon; + // clip by all but top + if (!clip_polygon_by_objsp_planes(polygon, { + { 2.0f, 0.0f, 0.0f, 1.0f}, + {-2.0f, 0.0f, 0.0f, 1.0f}, + {0.0f, 2.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 2.0f, 1.0f}, + {0.0f, 0.0f, -2.0f, 1.0f}, + })) + continue; + + // The top side of the water has two triangles, separated by the + // quad diagonal, so we need two convex polygons for the two convex + // polytopes that have these triangles as top + + // if there is no top face, the full cube is filled with liquid + auto top_corners_heights = getLiquidTopFaceHeights(npos, liquid_wave_params) + .value_or(std::array{0.5f, 0.5f, 0.5f, 0.5f}); + + std::array vertices{ + v4f{-0.5f, top_corners_heights[0], -0.5f, 1.0f}, + v4f{ 0.5f, top_corners_heights[1], -0.5f, 1.0f}, + v4f{-0.5f, top_corners_heights[2], 0.5f, 1.0f}, + v4f{ 0.5f, top_corners_heights[3], 0.5f, 1.0f}, + }; + + // For smooth lit solid nodes, the quad diagonal depends on lighting + QuadDiagonal quad_diag = QuadDiagonal::Diag02; + if (features.drawtype == NDT_LIQUID && smooth_lighting) { + // same as light_dirs[light_indices[0][0..4]] in content_mapblock.cpp + constexpr v3s16 corners[4] = { + v3s16(-1, 1, 1), + v3s16( 1, 1, 1), + v3s16( 1, 1, -1), + v3s16(-1, 1, -1), + }; + LightPair lights[4]; + for (int k = 0; k < 4; ++k) { + lights[k] = LightPair(getSmoothLightSolid(npos, v3s16(0,1,0), + corners[k], m_nodedef, + [this](v3s16 p) { return getNode(p); } + )); + } + quad_diag = LightPair::quadDiagonalForFace(lights); + } + v4f triangle_split_plane; + v4f triangle_split_plane_neg; + std::array indices; + if (quad_diag == QuadDiagonal::Diag02) { + triangle_split_plane = {-1.0f, 0.0f, -1.0f, 0.0f}; + triangle_split_plane_neg = { 1.0f, 0.0f, 1.0f, 0.0f}; + indices = {0, 1, 2, 1, 3, 2}; + } else { + triangle_split_plane = { 1.0f, 0.0f, -1.0f, 0.0f}; + triangle_split_plane_neg = {-1.0f, 0.0f, 1.0f, 0.0f}; + indices = {0, 1, 3, 0, 3, 2}; + } + + ConvexPolygon polygon1 = polygon; + ConvexPolygon &polygon2 = polygon; + + if (clip_polygon_by_objsp_plane(polygon1, triangle_split_plane) + && clip_polygon_by_objsp_plane(polygon1, + vector_cross_4d(vertices[indices[0]], vertices[indices[1]], + vertices[indices[2]]))) { + polygons.emplace_back(std::move(polygon1), post_color); + } + + if (clip_polygon_by_objsp_plane(polygon2, triangle_split_plane_neg) + && clip_polygon_by_objsp_plane(polygon2, + vector_cross_4d(vertices[indices[3]], vertices[indices[4]], + vertices[indices[5]]))) { + polygons.emplace_back(std::move(polygon2), post_color); + } + + } else { + // for other drawtypes: draw color fullscreen if camera in node + if (floatToInt(m_camera_position, BS) != npos) + continue; + polygons.emplace_back(screen_polygon, post_color); + } } - // If the camera is in a solid node, make everything black. - // (first person mode only) - if (features.solidness == 2 && cam_mode == CAMERA_MODE_FIRST && - !m_control.allow_noclip) { - post_color = video::SColor(255, 0, 0, 0); + return polygons; +} + +video::SColorf ClientMap::renderPostFx() +{ + auto polygons = getPostFxPolygons(); + + video::IVideoDriver *driver = SceneManager->getVideoDriver(); + + for (const auto &[polygon, post_color] : polygons) { + polygon.draw(driver, post_color); } - if (post_color.getAlpha() != 0) { - // Draw a full-screen rectangle - video::IVideoDriver* driver = SceneManager->getVideoDriver(); - v2u32 ss = driver->getScreenSize(); - core::rect rect(0,0, ss.X, ss.Y); - driver->draw2DRectangle(post_color, rect); + // Color for wield item + // We take the weighted (by area and alpha) sum of all polygons. + // The result is alpha-premultiplied. + video::SColorf color_sum(0.0f, 0.0f, 0.0f, 0.0f); + + for (const auto &[polygon, post_color] : polygons) { + video::SColorf color = post_color; + + // (screen size and aspect ratio don't matter for relative screen area) + f32 area = polygon.area(); + color.a *= area; + + color_sum.r += color.a * color.r; + color_sum.g += color.a * color.g; + color_sum.b += color.a * color.b; + color_sum.a = color_sum.a + color.a; } + + if (color_sum.a > 1.0f) { + f32 reci_a = 1.0f / color_sum.a; + color_sum.a *= reci_a; + color_sum.r *= reci_a; + color_sum.g *= reci_a; + color_sum.b *= reci_a; + } + + return color_sum; } void ClientMap::PrintInfo(std::ostream &out) @@ -1485,6 +1759,124 @@ void ClientMap::updateTransparentMeshBuffers() m_needs_update_transparent_meshes = false; } +std::optional> +ClientMap::getLiquidTopFaceHeights(v3s16 pos, + const std::optional &wave_params) +{ + std::optional> heights_opt = std::nullopt; + + const content_t node_c = getNode(pos).getContent(); + const ContentFeatures &node_f = m_nodedef->get(node_c); + const content_t node_above_c = getNode(pos + v3s16(0, 1, 0)).getContent(); + + if (node_f.drawtype == NDT_LIQUID) { + if (MapblockMeshGenerator::isSolidFaceDrawn(node_c, node_f, node_above_c, + m_nodedef)) + heights_opt = std::array{0.5f, 0.5f, 0.5f, 0.5f}; + + } else if (node_f.drawtype == NDT_FLOWINGLIQUID) { + using LiquidNeighborData = MapblockMeshGenerator::LiquidData::NeighborData; + + content_t c_flowing = node_f.liquid_alternative_flowing_id; + content_t c_source = node_f.liquid_alternative_source_id; + u8 liquid_range = node_f.liquid_range; + auto get_node_rel = [&](v3s16 relpos) -> MapNode { + return getNode(pos + relpos); + }; + + std::array, 3> neighbors = + MapblockMeshGenerator::getLiquidNeighborsData(c_source, c_flowing, + liquid_range, get_node_rel); + + if (!neighbors[1][1].top_is_same_liquid) { + std::array, 2> corners = + MapblockMeshGenerator::calculateLiquidCornerLevels(c_source, + c_flowing, neighbors); + + heights_opt = { + corners[0][0], corners[0][1], + corners[1][0], corners[1][1] + }; + } + } + + if (!heights_opt || !wave_params || node_f.waving != 3) + return heights_opt; + + // do the wave + // Note: The same thing is implemented in the nodes vertex shader. Keep + // them consistent! + auto wave_func = [&wave_params](v3f wavePos) { + using v4f = std::array; + + // Why does C++ still not have a normal mod function?! + auto mod = [](f32 a, f32 b) { + return a - b * std::floor(a/b); + }; + + // + // Simple, fast noise function. + // See: https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83 + // + auto perm = [&mod](v4f v) -> v4f { + auto f = [&](f32 x) { return mod(((x * 34.0f) + 1.0f) * x, 289.0f); }; + return v4f{f(v[0]), f(v[1]), f(v[2]), f(v[3])}; + }; + + auto snoise = [&perm, &mod](v3f p) -> f32 { + auto v4add = [](v4f v1, v4f v2) { + return v4f{v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2], v1[3] + v2[3]}; + }; + auto v4adds = [](v4f v, f32 s) { + return v4f{v[0] + s, v[1] + s, v[2] + s, v[3] + s}; + }; + auto v4scale = [](v4f v, f32 s) { + return v4f{v[0] * s, v[1] * s, v[2] * s, v[3] * s}; + }; + auto v3schur = [](v3f v1, v3f v2) { + return v3f{v1.X * v2.X, v1.Y * v2.Y, v1.Z * v2.Z}; + }; + + v3f a = v3f{std::floor(p.X), std::floor(p.Y), std::floor(p.Z)}; + v3f d = p - a; + d = v3schur(v3schur(d, d), v3f(3.0f) - 2.0f * d); + + v4f b = v4add(v4f{a.X, a.X, a.Y, a.Y}, v4f{0.0f, 1.0f, 0.0f, 1.0f}); + v4f k1 = perm(v4f{b[0], b[1], b[0], b[1]}); + v4f k2 = perm(v4add(v4f{k1[0], k1[1], k1[0], k1[1]}, v4f{b[2], b[2], b[3], b[3]})); + + v4f c = v4adds(k2, a.Z); + v4f k3 = perm(c); + v4f k4 = perm(v4adds(c, 1.0f)); + + auto f1 = [&](f32 x) { return mod(x * (1.0f / 41.0f), 1.0f); }; + v4f o1 = {f1(k3[0]), f1(k3[1]), f1(k3[2]), f1(k3[3])}; + v4f o2 = {f1(k4[0]), f1(k4[1]), f1(k4[2]), f1(k4[3])}; + + v4f o3 = v4add(v4scale(o2, d.Z), v4scale(o1, 1.0f - d.Z)); + v2f o4 = v2f{o3[1], o3[3]} * d.X + v2f{o3[0], o3[2]} * (1.0f - d.X); + + return o4.Y * d.Y + o4.X * (1.0f - d.Y); + }; + + // The waves are slightly compressed along the z-axis to get + // wave-fronts along the x-axis. + wavePos.X /= wave_params->length * 3.0f; + wavePos.Z /= wave_params->length * 2.0f; + wavePos.Z += wave_params->animation_timer * wave_params->speed * 10.0f; + return (snoise(wavePos) - 1.0f) * wave_params->height * 5.0f; + }; + + const v3f pos_bs = intToFloat(pos, BS); + auto &heights = *heights_opt; + heights[0] += wave_func(pos_bs + BS * v3f(-0.5f, heights[0], -0.5f)) * (1.0f/BS); + heights[1] += wave_func(pos_bs + BS * v3f( 0.5f, heights[1], -0.5f)) * (1.0f/BS); + heights[2] += wave_func(pos_bs + BS * v3f(-0.5f, heights[2], 0.5f)) * (1.0f/BS); + heights[3] += wave_func(pos_bs + BS * v3f( 0.5f, heights[3], 0.5f)) * (1.0f/BS); + + return heights_opt; +} + video::SMaterial &DrawDescriptor::getMaterial() { return (m_use_partial_buffer ? m_partial_buffer->getBuffer() : m_buffer)->getMaterial(); diff --git a/src/client/clientmap.h b/src/client/clientmap.h index 760a1a4db..fbcfed55c 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -10,6 +10,8 @@ #include #include +struct ConvexPolygon; + struct MapDrawControl { // Wanted drawing range @@ -22,6 +24,13 @@ struct MapDrawControl bool show_wireframe = false; }; +struct LiquidWaveParams { + f32 height; + f32 length; + f32 speed; + f32 animation_timer; +}; + class Client; class ITextureSource; class PartialMeshBuffer; @@ -93,7 +102,8 @@ public: int getBackgroundBrightness(float max_d, u32 daylight_factor, int oldvalue, bool *sunlight_seen_result); - void renderPostFx(CameraMode cam_mode); + // Returns the post effect color for the wield hand + video::SColorf renderPostFx(); // For debug printing void PrintInfo(std::ostream &out) override; @@ -102,6 +112,24 @@ public: f32 getWantedRange() const { return m_control.wanted_range; } f32 getCameraFov() const { return m_camera_fov; } + /** + * Finds the heights of the corners of the top face of a liquid node at the + * given pos. + * + * Note: Liquid top faces are not quads, but two triangles, split by a + * diagonal. + * + * @param pos The position of the node. + * @param wave_params If given, the corner heights are waved like in the node + * shader. + * @return The heights of the corners in local node space coordinates, in the + * following order: {-x-z, +x-z, -x+z, +x+z} + * Or nullopt if there is no lquid top face. + */ + std::optional> + getLiquidTopFaceHeights(v3s16 pos, + const std::optional &wave_params); + void onSettingChanged(std::string_view name, bool all); protected: @@ -109,12 +137,16 @@ protected: virtual ~ClientMap(); void reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) override; + private: bool isMeshOccluded(MapBlock *mesh_block, u16 mesh_size, v3s16 cam_pos_nodes); // update the vertex order in transparent mesh buffers void updateTransparentMeshBuffers(); + // helper for renderPostFx + std::vector> getPostFxPolygons(); + // Orders blocks by distance to the camera class MapBlockComparer { diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 5b69b4a5d..18112859b 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -18,6 +18,8 @@ #include "client.h" #include "noise.h" +using LiquidData = MapblockMeshGenerator::LiquidData; + // Distance of light extrapolation (for oversized nodes) // After this distance, it gives up and considers light level constant #define SMOOTH_LIGHTING_OVERSIZE 1.0 @@ -57,6 +59,20 @@ static constexpr u16 quad_indices_02[] = {0, 1, 2, 2, 3, 0}; static constexpr u16 quad_indices_13[] = {0, 1, 3, 3, 1, 2}; static const auto &quad_indices = quad_indices_02; +int LightPair::lightDiff(LightPair a, LightPair b) +{ + return std::abs(a.lightDay - b.lightDay) + std::abs(a.lightNight - b.lightNight); +} + +QuadDiagonal LightPair::quadDiagonalForFace(LightPair final_lights[4]) +{ + if (lightDiff(final_lights[1], final_lights[3]) + < lightDiff(final_lights[0], final_lights[2])) + return QuadDiagonal::Diag13; + else + return QuadDiagonal::Diag02; +} + const std::string MapblockMeshGenerator::raillike_groupname = "connect_to_raillike"; MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output): @@ -198,11 +214,6 @@ static std::array setupCuboidVertices(const aabb3f &box, return vertices; } -enum class QuadDiagonal { - Diag02, - Diag13, -}; - // Create a cuboid with custom lighting. // tiles - the tiles (materials) to use (for all 6 faces) // tilecount - number of entries in tiles, 1<=tilecount<=6 @@ -317,11 +328,6 @@ void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 * coords[i] = txc[i]; } -static inline int lightDiff(LightPair a, LightPair b) -{ - return abs(a.lightDay - b.lightDay) + abs(a.lightNight - b.lightNight); -} - void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const TileSpec &tile, const f32 *txc, u8 mask) { @@ -372,9 +378,7 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, if (!cur_node.f->light_source) applyFacesShading(vertex.Color, vertex.Normal); } - if (lightDiff(final_lights[1], final_lights[3]) < lightDiff(final_lights[0], final_lights[2])) - return QuadDiagonal::Diag13; - return QuadDiagonal::Diag02; + return LightPair::quadDiagonalForFace(final_lights); }); } else { drawCuboid(box, tiles, tile_count, txc, mask, [&] (int face, video::S3DVertex vertices[4]) { @@ -390,6 +394,30 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, } } +bool MapblockMeshGenerator::isSolidFaceDrawn(content_t n1, const ContentFeatures &f1, + content_t n2, const NodeDefManager *nodedef, bool *backface_culling_out) +{ + bool backface_culling = f1.drawtype == NDT_NORMAL; + if (n2 == n1) + return false; + if (n2 == CONTENT_IGNORE) + return false; + if (n2 != CONTENT_AIR) { + const ContentFeatures &f2 = nodedef->get(n2); + if (f2.solidness == 2) + return false; + if (f1.drawtype == NDT_LIQUID) { + if (f1.sameLiquidRender(f2)) + return false; + backface_culling = f2.solidness || f2.visual_solidness; + } + } + + if (backface_culling_out) + *backface_culling_out = backface_culling; + return true; +} + void MapblockMeshGenerator::drawSolidNode() { u8 faces = 0; // k-th bit will be set if k-th face is to be drawn. @@ -408,21 +436,9 @@ void MapblockMeshGenerator::drawSolidNode() v3s16 p2 = blockpos_nodes + cur_node.p + tile_dirs[face]; MapNode neighbor = data->m_vmanip.getNodeNoEx(p2); content_t n2 = neighbor.getContent(); - bool backface_culling = cur_node.f->drawtype == NDT_NORMAL; - if (n2 == n1) + bool backface_culling; + if (!isSolidFaceDrawn(n1, *cur_node.f, n2, nodedef, &backface_culling)) continue; - if (n2 == CONTENT_IGNORE) - continue; - if (n2 != CONTENT_AIR) { - const ContentFeatures &f2 = nodedef->get(n2); - if (f2.solidness == 2) - continue; - if (cur_node.f->drawtype == NDT_LIQUID) { - if (cur_node.f->sameLiquidRender(f2)) - continue; - backface_culling = f2.solidness || f2.visual_solidness; - } - } faces |= 1 << face; getTile(tile_dirs[face], &tiles[face]); for (auto &layer : tiles[face].layers) { @@ -464,9 +480,7 @@ void MapblockMeshGenerator::drawSolidNode() if (!cur_node.f->light_source) applyFacesShading(vertex.Color, vertex.Normal); } - if (lightDiff(final_lights[1], final_lights[3]) < lightDiff(final_lights[0], final_lights[2])) - return QuadDiagonal::Diag13; - return QuadDiagonal::Diag02; + return LightPair::quadDiagonalForFace(final_lights); }); } else { drawCuboid(box, tiles, 6, texture_coord_buf, mask, [&] (int face, video::S3DVertex vertices[4]) { @@ -557,15 +571,19 @@ void MapblockMeshGenerator::prepareLiquidNodeDrawing() cur_node.lcolor = encode_light(light, cur_node.f->light_source); } -void MapblockMeshGenerator::getLiquidNeighborhood() +template +std::array, 3> +MapblockMeshGenerator::getLiquidNeighborsDataRaw( + content_t c_source, content_t c_flowing, u8 liquid_range, F &&get_node_rel) { - u8 range = rangelim(nodedef->get(cur_liquid.c_flowing).liquid_range, 1, 8); + std::array, 3> ret; + + u8 range = rangelim(liquid_range, 1, 8); for (int w = -1; w <= 1; w++) for (int u = -1; u <= 1; u++) { - LiquidData::NeighborData &neighbor = cur_liquid.neighbors[w + 1][u + 1]; - v3s16 p2 = cur_node.p + v3s16(u, 0, w); - MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); + LiquidData::NeighborData &neighbor = ret[w + 1][u + 1]; + const MapNode n2 = get_node_rel(v3s16(u, 0, w)); neighbor.content = n2.getContent(); neighbor.level = -0.5f; neighbor.is_same_liquid = false; @@ -574,10 +592,10 @@ void MapblockMeshGenerator::getLiquidNeighborhood() if (neighbor.content == CONTENT_IGNORE) continue; - if (neighbor.content == cur_liquid.c_source) { + if (neighbor.content == c_source) { neighbor.is_same_liquid = true; neighbor.level = 0.5f; - } else if (neighbor.content == cur_liquid.c_flowing) { + } else if (neighbor.content == c_flowing) { neighbor.is_same_liquid = true; u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK); if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range) @@ -590,28 +608,41 @@ void MapblockMeshGenerator::getLiquidNeighborhood() // Check node above neighbor. // NOTE: This doesn't get executed if neighbor // doesn't exist - p2.Y++; - n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); - if (n2.getContent() == cur_liquid.c_source || n2.getContent() == cur_liquid.c_flowing) + const MapNode n3 = get_node_rel(v3s16(u, 1, w)); + if (n3.getContent() == c_source || n3.getContent() == c_flowing) neighbor.top_is_same_liquid = true; } + + return ret; } -void MapblockMeshGenerator::calculateCornerLevels() +std::array, 3> +MapblockMeshGenerator::getLiquidNeighborsData( + content_t c_source, content_t c_flowing, u8 liquid_range, + const std::function &get_node_rel) { - for (int k = 0; k < 2; k++) - for (int i = 0; i < 2; i++) - cur_liquid.corner_levels[k][i] = getCornerLevel(i, k); + return getLiquidNeighborsDataRaw(c_source, c_flowing, liquid_range, get_node_rel); } -f32 MapblockMeshGenerator::getCornerLevel(int i, int k) const +void MapblockMeshGenerator::getLiquidNeighborhood() +{ + u8 liquid_range = nodedef->get(cur_liquid.c_flowing).liquid_range; + auto get_node_rel = [&](v3s16 relpos) { + return data->m_vmanip.getNodeNoEx(blockpos_nodes + cur_node.p + relpos); + }; + + cur_liquid.neighbors = getLiquidNeighborsDataRaw(cur_liquid.c_source, + cur_liquid.c_flowing, liquid_range, get_node_rel); +} + +f32 MapblockMeshGenerator::calculateLiquidCornerLevel( + content_t c_source, content_t c_flowing, + const std::array, 4> &neighbors) { float sum = 0; int count = 0; int air_count = 0; - for (int dk = 0; dk < 2; dk++) - for (int di = 0; di < 2; di++) { - const LiquidData::NeighborData &neighbor_data = cur_liquid.neighbors[k + dk][i + di]; + for (const LiquidData::NeighborData &neighbor_data : neighbors) { content_t content = neighbor_data.content; // If top is liquid, draw starting from top of node @@ -619,11 +650,11 @@ f32 MapblockMeshGenerator::getCornerLevel(int i, int k) const return 0.5f; // Source always has the full height - if (content == cur_liquid.c_source) + if (content == c_source) return 0.5f; // Flowing liquid has level information - if (content == cur_liquid.c_flowing) { + if (content == c_flowing) { sum += neighbor_data.level; count++; } else if (content == CONTENT_AIR) { @@ -637,6 +668,30 @@ f32 MapblockMeshGenerator::getCornerLevel(int i, int k) const return 0; } +std::array, 2> +MapblockMeshGenerator::calculateLiquidCornerLevels( + content_t c_source, content_t c_flowing, + const std::array, 3> &neighbors) +{ + std::array, 2> corners; + + for (int k = 0; k < 2; k++) { + for (int i = 0; i < 2; i++) { + corners[k][i] = calculateLiquidCornerLevel(c_source, c_flowing, { + std::cref(neighbors[k][i]), std::cref(neighbors[k][i+1]), + std::cref(neighbors[k+1][i]), std::cref(neighbors[k+1][i+1]), + }); + }} + + return corners; +} + +void MapblockMeshGenerator::calculateCornerLevels() +{ + cur_liquid.corner_levels = calculateLiquidCornerLevels(cur_liquid.c_source, + cur_liquid.c_flowing, cur_liquid.neighbors); +} + namespace { struct LiquidFaceDesc { v3s16 dir; // XZ diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index 2bfbbdc61..f414741ce 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -5,10 +5,16 @@ #pragma once #include "nodedef.h" +#include struct MeshMakeData; struct MeshCollector; +enum class QuadDiagonal { + Diag02, + Diag13, +}; + struct LightPair { u8 lightDay; u8 lightNight; @@ -20,6 +26,9 @@ struct LightPair { lightDay(core::clamp(core::round32(valueA), 0, 255)), lightNight(core::clamp(core::round32(valueB), 0, 255)) {} operator u16() const { return lightDay | lightNight << 8; } + + static int lightDiff(LightPair a, LightPair b); + static QuadDiagonal quadDiagonalForFace(LightPair final_lights[4]); }; struct LightInfo { @@ -92,10 +101,16 @@ private: void drawAutoLightedCuboid(aabb3f box, const TileSpec *tiles, int tile_count, f32 const *txc = nullptr, u8 mask = 0); u8 getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const; +// solid-specific +public: + static bool isSolidFaceDrawn(content_t n1, const ContentFeatures &f1, + content_t n2, const NodeDefManager *nodedef, + bool *backface_culling_out = nullptr); + // liquid-specific struct LiquidData { struct NeighborData { - f32 level; + f32 level; // in range [-0.5, 0.5] content_t content; bool is_same_liquid; bool top_is_same_liquid; @@ -108,15 +123,32 @@ private: content_t c_flowing; content_t c_source; video::SColor color_top; - NeighborData neighbors[3][3]; - f32 corner_levels[2][2]; + std::array, 3> neighbors; + std::array, 2> corner_levels; }; LiquidData cur_liquid; + static std::array, 3> + getLiquidNeighborsData(content_t c_source, content_t c_flowing, u8 liquid_range, + const std::function &get_node_rel); + + // the arrays are indexed Z-first, i.e. neighbors[z][x], ret[z][x] + static std::array, 2> + calculateLiquidCornerLevels(content_t c_source, content_t c_flowing, + const std::array, 3> &neighbors); + +private: + template + static std::array, 3> + getLiquidNeighborsDataRaw(content_t c_source, content_t c_flowing, + u8 liquid_range, F &&get_node_rel); + + static f32 calculateLiquidCornerLevel(content_t c_source, content_t c_flowing, + const std::array, 4> &neighbors); + void prepareLiquidNodeDrawing(); void getLiquidNeighborhood(); void calculateCornerLevels(); - f32 getCornerLevel(int i, int k) const; void drawLiquidSides(); void drawLiquidTop(); void drawLiquidBottom(); diff --git a/src/client/game.cpp b/src/client/game.cpp index a4a4c9909..8986c8625 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -223,6 +223,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_bloom_strength_pixel{"bloomStrength"}; CachedPixelShaderSetting m_bloom_radius_pixel{"bloomRadius"}; CachedPixelShaderSetting m_saturation_pixel{"saturation"}; + CachedPixelShaderSetting + m_wield_posteffect_color{"wield_posteffect_color"}; bool m_volumetric_light_enabled; CachedPixelShaderSetting m_sun_position_pixel{"sunPositionScreen"}; @@ -336,6 +338,8 @@ public: float saturation = lighting.saturation; m_saturation_pixel.set(&saturation, services); + m_wield_posteffect_color.set(m_client->getWieldPostEffectColor(), services); + if (m_volumetric_light_enabled) { // Map directional light to screen space auto camera_node = m_client->getCamera()->getCameraNode(); diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 05f9e73cc..238fa0615 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -128,11 +128,10 @@ u16 getFaceLight(MapNode n, MapNode n2, const NodeDefManager *ndef) Calculate smooth lighting at the XYZ- corner of p. Both light banks */ +template static u16 getSmoothLightCombined(const v3s16 &p, - const std::array &dirs, MeshMakeData *data) + const std::array &dirs, const NodeDefManager *ndef, F &&get_node) { - const NodeDefManager *ndef = data->m_nodedef; - u16 ambient_occlusion = 0; u16 light_count = 0; u8 light_source_max = 0; @@ -145,7 +144,7 @@ static u16 getSmoothLightCombined(const v3s16 &p, ambient_occlusion++; return false; } - MapNode n = data->m_vmanip.getNodeNoExNoEmerge(p + dirs[i]); + MapNode n = get_node(p + dirs[i]); if (n.getContent() == CONTENT_IGNORE) return true; const ContentFeatures &f = ndef->get(n); @@ -232,22 +231,14 @@ static u16 getSmoothLightCombined(const v3s16 &p, return light_day | (light_night << 8); } -/* - Calculate smooth lighting at the given corner of p. - Both light banks. - Node at p is solid, and thus the lighting is face-dependent. -*/ -u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data) -{ - return getSmoothLightTransparent(p + face_dir, corner - 2 * face_dir, data); -} - /* Calculate smooth lighting at the given corner of p. Both light banks. Node at p is not solid, and the lighting is not face-dependent. */ -u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data) +template +static u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, + const NodeDefManager *ndef, F &&get_node) { const std::array dirs = {{ // Always shine light @@ -262,7 +253,35 @@ u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData v3s16(0,corner.Y,corner.Z), v3s16(corner.X,corner.Y,corner.Z) }}; - return getSmoothLightCombined(p, dirs, data); + + return getSmoothLightCombined(p, dirs, ndef, std::forward(get_node)); +} + +u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data) +{ + return getSmoothLightTransparent(p, corner, data->m_nodedef, + [data](v3s16 p) -> MapNode { + return data->m_vmanip.getNodeNoExNoEmerge(p); + } + ); +} + +/* + Calculate smooth lighting at the given corner of p. + Both light banks. + Node at p is solid, and thus the lighting is face-dependent. +*/ +u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, + MeshMakeData *data) +{ + return getSmoothLightTransparent(p + face_dir, corner - 2 * face_dir, data); +} + +u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, + const NodeDefManager *ndef, const std::function &get_node) +{ + return getSmoothLightTransparent(p + face_dir, corner - 2 * face_dir, + ndef, get_node); } void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio) diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index 47941386e..d5faf4486 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -296,6 +296,8 @@ video::SColor encode_light(u16 light, u8 emissive_light); u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef); u16 getFaceLight(MapNode n, MapNode n2, const NodeDefManager *ndef); u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data); +u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, + const NodeDefManager *ndef, const std::function &get_node); u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data); /*! diff --git a/src/client/render/anaglyph.cpp b/src/client/render/anaglyph.cpp index 833ad0114..7666e34c4 100644 --- a/src/client/render/anaglyph.cpp +++ b/src/client/render/anaglyph.cpp @@ -71,7 +71,7 @@ void populateAnaglyphPipeline(RenderPipeline *pipeline, Client *client) pipeline->addStep(0.0f); pipeline->addStep(video::ECP_ALL); - pipeline->addStep(); pipeline->addStep(); + pipeline->addStep(); pipeline->addStep(); } diff --git a/src/client/render/interlaced.cpp b/src/client/render/interlaced.cpp index c4014a74d..521953899 100644 --- a/src/client/render/interlaced.cpp +++ b/src/client/render/interlaced.cpp @@ -61,8 +61,8 @@ void populateInterlacedPipeline(RenderPipeline *pipeline, Client *client) auto output = pipeline->createOwned(buffer, right ? TEXTURE_RIGHT : TEXTURE_LEFT); pipeline->addStep(step3D, output); pipeline->addStep(step3D); - pipeline->addStep(); pipeline->addStep(); + pipeline->addStep(); } pipeline->addStep(0.0f); diff --git a/src/client/render/pipeline.h b/src/client/render/pipeline.h index 17bed8b7b..e7b361b6d 100644 --- a/src/client/render/pipeline.h +++ b/src/client/render/pipeline.h @@ -36,6 +36,7 @@ struct PipelineContext ShadowRenderer *shadow_renderer; video::SColor clear_color; v2u32 target_size; + video::SColorf wield_post_color{0.0f, 0.0f, 0.0f, 0.0f}; bool show_hud {true}; bool draw_wield_tool {true}; diff --git a/src/client/render/plain.cpp b/src/client/render/plain.cpp index c24ba8837..0d3077b95 100644 --- a/src/client/render/plain.cpp +++ b/src/client/render/plain.cpp @@ -32,7 +32,7 @@ void DrawWield::run(PipelineContext &context) m_target->activate(context); if (context.draw_wield_tool) - context.client->getCamera()->drawWieldedTool(); + context.client->getCamera()->drawWieldedTool(nullptr, context.wield_post_color); } void DrawHUD::run(PipelineContext &context) @@ -63,7 +63,8 @@ void MapPostFxStep::run(PipelineContext &context) if (target) target->activate(context); - context.client->getEnv().getClientMap().renderPostFx(context.client->getCamera()->getCameraMode()); + context.wield_post_color = context.client->getEnv().getClientMap() + .renderPostFx(); } void RenderShadowMapStep::run(PipelineContext &context) @@ -143,8 +144,8 @@ void populatePlainPipeline(RenderPipeline *pipeline, Client *client) auto downscale_factor = getDownscaleFactor(); auto step3D = pipeline->own(create3DStage(client, downscale_factor)); pipeline->addStep(step3D); - pipeline->addStep(); pipeline->addStep(); + pipeline->addStep(); step3D = addUpscaling(pipeline, step3D, downscale_factor, client); diff --git a/src/client/render/plain.h b/src/client/render/plain.h index b0ce29608..c9984ef76 100644 --- a/src/client/render/plain.h +++ b/src/client/render/plain.h @@ -23,6 +23,9 @@ private: RenderTarget *m_target {nullptr}; }; +/** + * Implements a pipeline step that renders the wieldhand + */ class DrawWield : public RenderStep { public: diff --git a/src/client/render/sidebyside.cpp b/src/client/render/sidebyside.cpp index 57d5b474e..0c03a159b 100644 --- a/src/client/render/sidebyside.cpp +++ b/src/client/render/sidebyside.cpp @@ -66,8 +66,8 @@ void populateSideBySidePipeline(RenderPipeline *pipeline, Client *client, bool h buffer, std::vector {right ? TEXTURE_RIGHT : TEXTURE_LEFT}, TEXTURE_DEPTH); pipeline->addStep(step3D, output); pipeline->addStep(step3D); - pipeline->addStep(); pipeline->addStep(); + pipeline->addStep(); pipeline->addStep(); } @@ -82,4 +82,4 @@ void populateSideBySidePipeline(RenderPipeline *pipeline, Client *client, bool h step->setRenderSource(buffer); step->setRenderTarget(screen); } -} \ No newline at end of file +} diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index ec88a33c2..9ac84dcfa 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -3,6 +3,7 @@ set(util_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/auth.cpp ${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp ${CMAKE_CURRENT_SOURCE_DIR}/colorize.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/convex_polygon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp ${CMAKE_CURRENT_SOURCE_DIR}/enriched_string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/hashing.cpp diff --git a/src/util/convex_polygon.cpp b/src/util/convex_polygon.cpp new file mode 100644 index 000000000..079c9f629 --- /dev/null +++ b/src/util/convex_polygon.cpp @@ -0,0 +1,159 @@ +// SPDX-FileCopyrightText: 2024 Minetest +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "convex_polygon.h" +#include + +#ifndef SERVER + +#include + +void ConvexPolygon::draw(video::IVideoDriver *driver, const video::SColor &color) const +{ + if (vertices.size() < 3) + return; + + auto new_2d_vertex = [&color](const v2f &pos) -> video::S3DVertex { + return video::S3DVertex( + v3f(pos.X * 2.0f - 1.0f, -(pos.Y * 2.0f - 1.0f), 0.0f), + v3f(), color, v2f()); + }; + + std::vector s3d_vertices; + s3d_vertices.reserve(vertices.size()); + for (const v2f &v : vertices) + s3d_vertices.push_back(new_2d_vertex(v)); + + std::vector index_list; + index_list.reserve(vertices.size()); + for (size_t i = 0; i < vertices.size(); ++i) + index_list.push_back(i); + + video::SMaterial material; + material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; + material.ZWriteEnable = video::EZW_OFF; + driver->setMaterial(material); + + driver->setTransform(video::ETS_PROJECTION, core::matrix4::EM4CONST_IDENTITY); + driver->setTransform(video::ETS_VIEW, core::matrix4::EM4CONST_IDENTITY); + driver->setTransform(video::ETS_WORLD, core::matrix4::EM4CONST_IDENTITY); + + driver->drawVertexPrimitiveList((void *)&s3d_vertices[0], (u32)s3d_vertices.size(), + (void *)&index_list[0], (u32)index_list.size() - 2, + video::EVT_STANDARD, // S3DVertex vertices + scene::EPT_TRIANGLE_FAN, + video::EIT_16BIT); // 16 bit indices +} + +#endif // !def(SERVER) + +f32 ConvexPolygon::area() const +{ + if (vertices.size() < 3) + return 0.0f; + // sum up the areas of all triangles + f32 area = 0.0f; + const v2f &v1 = vertices[0]; + for (size_t i = 2; i < vertices.size(); ++i) { + const v2f &v2 = vertices[i-1]; + const v2f &v3 = vertices[i]; + // area of the triangle v1 v2 v3: 0.5 * det(d1 d2) + // (winding order matters, for sign) + v2f d1 = v2 - v1; + v2f d2 = v3 - v1; + area += 0.5f * (d1.X * d2.Y - d1.Y * d2.X); + } + return area; +} + +ConvexPolygon ConvexPolygon::clip(v3f clip_line) const +{ + using polygon_iterator = std::vector::const_iterator; + + // the return value + ConvexPolygon clipped; + auto &vertices_out = clipped.vertices; + + if (vertices.empty()) + return clipped; // emtpty + + // returns whether pos is in the not clipped half-space + auto is_in = [&](const v2f &pos) { + return pos.X * clip_line.X + pos.Y * clip_line.Y + clip_line.Z >= 0.0f; + }; + + auto is_out = [&](const v2f &pos) { + return !is_in(pos); + }; + + // There are 3 cases: + // * all vertices are clipped away + // * no vertices are clipped away + // * clip_line intersects the polygon at two points. + // There is hence one (possibly over the list end) contiguous sub-list of + // vertices that are all in (not clipped) + // + // is_in applied on the polygon vertices looks like this: + // {in, in, in, out, out, in, in} + // last_in ^ ^ first_in + // first_out ^ ^ last_out + // + // We need to cut the polygon between the out-in and in-out vertex pairs. + + // find the first vertex that is in/out of the clip_line, and also the vertex + // before + polygon_iterator first_in, first_out; + polygon_iterator last_out, last_in; + if (is_in(vertices[0])) { + first_out = std::find_if(vertices.begin() + 1, vertices.end(), is_out); + if (first_out == vertices.end()) { + // all are in + clipped = {vertices}; + return clipped; + } + last_in = first_out - 1; + first_in = std::find_if(first_out + 1, vertices.end(), is_in); + last_out = first_in - 1; + if (first_in == vertices.end()) + first_in = vertices.begin(); // we already checked that the 0th is in + } else { + first_in = std::find_if(vertices.begin() + 1, vertices.end(), is_in); + if (first_in == vertices.end()) { + // all are out + return clipped; // empty + } + last_out = first_in - 1; + first_out = std::find_if(first_in + 1, vertices.end(), is_out); + last_in = first_out - 1; + if (first_out == vertices.end()) + first_out = vertices.begin(); // we already checked that the 0th is out + } + + // copy all vertices that are in + if (first_in <= last_in) { + vertices_out.reserve((last_in - first_in) + 1 + 2); + vertices_out.insert(vertices_out.end(), first_in, last_in + 1); + } else { + vertices_out.reserve((vertices.end() - first_in) + (last_in - vertices.begin()) + 1 + 2); + vertices_out.insert(vertices_out.end(), first_in, vertices.end()); + vertices_out.insert(vertices_out.end(), vertices.begin(), last_in + 1); + } + + auto split_edge = [&](const v2f &p1, const v2f &p2) { + auto homogenize = [](const v2f &p) { return v3f(p.X, p.Y, 1.0f); }; + // find a pos that is on clip_line and between p1 and p2 + // compute: meet(clip_line, join(p1, p2)) + v3f pos_homo = clip_line.crossProduct(homogenize(p1).crossProduct(homogenize(p2))); + // dehomogenize + return v2f(pos_homo.X / pos_homo.Z, pos_homo.Y / pos_homo.Z); + }; + + // split in-out pair + vertices_out.push_back(split_edge(*last_in, *first_out)); + + // split out-in pair + vertices_out.push_back(split_edge(*last_out, *first_in)); + + return clipped; +} diff --git a/src/util/convex_polygon.h b/src/util/convex_polygon.h new file mode 100644 index 000000000..a717638c1 --- /dev/null +++ b/src/util/convex_polygon.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2024 Minetest +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "irr_v2d.h" +#include "irr_v3d.h" +#include +#include + +#ifndef SERVER + +namespace irr::video { class IVideoDriver; } +#endif // !def(SERVER) + +/** + * A polygon, stored as closed polyonal chain (ordered vertex list). + * The functions assume it's convex. + */ +struct ConvexPolygon +{ + std::vector vertices; + +#ifndef SERVER + + /** Draws the polygon over the whole screen. + * + * X coordinates go from 0 (left) to 1 (right), Y from 0 (up) to 1 (down). + */ + void draw(video::IVideoDriver *driver, const video::SColor &color) const; + +#endif // !def(SERVER) + + /** Calculates the area. + * + * @return The area. + */ + [[nodiscard]] + f32 area() const; + + /** Clips away the parts of the polygon that are on the wrong side of the + * given clip line. + * @param clip_line The homogeneous coordinates of an oriented 2D line. Clips + * away all points p for which dot(clip_line, p) < 0 holds. + * @return The clipped polygon. + */ + [[nodiscard]] + ConvexPolygon clip(v3f clip_line) const; +};