#include "EterLib/StdAfx.h" #include "UIRenderCommands.h" #include "RenderCommands3D.h" #include "EterLib/StateManager.h" #include namespace { struct Point { float x, y; }; std::vector clip_polygon(std::vector polygon, int edge, float bound) { std::vector result; if (polygon.empty()) return result; auto inside = [edge, bound](Point p) { return edge == 0 ? p.x >= bound : edge == 1 ? p.x <= bound : edge == 2 ? p.y >= bound : p.y <= bound; }; auto intersect = [edge, bound](Point a, Point b) { const float t = edge < 2 ? (bound - a.x) / (b.x - a.x) : (bound - a.y) / (b.y - a.y); return Point{a.x + t * (b.x - a.x), a.y + t * (b.y - a.y)}; }; Point previous = polygon.back(); for (Point current : polygon) { const bool was_inside = inside(previous), is_inside = inside(current); if (was_inside != is_inside) result.push_back(intersect(previous, current)); if (is_inside) result.push_back(current); previous = current; } return result; } std::vector commands; std::uint64_t frame_id = 0; int canvas_width = 0; int canvas_height = 0; float clip_x1 = 0, clip_y1 = 0, clip_x2 = 0, clip_y2 = 0; float saved_x1 = 0, saved_y1 = 0, saved_x2 = 0, saved_y2 = 0; float offset_x = 0; } void UIRenderSetOffsetX(float x) { offset_x = x; } float UIRenderOffsetX() { return offset_x; } void UIRenderSetSize(int width, int height) { canvas_width = width; canvas_height = height; } void UIRenderGetSize(unsigned* width, unsigned* height) { if (width) *width = canvas_width; if (height) *height = canvas_height; } void UIRenderBeginFrame() { ++frame_id; commands.clear(); clip_x1 = clip_y1 = 0; clip_x2 = canvas_width; clip_y2 = canvas_height; } std::uint64_t UIRenderFrameId() { return frame_id; } void UIRenderAdd(UIRenderCommand command) { if (offset_x != 0) { command.x1 += offset_x; command.x2 += offset_x; if (command.quad) for (float& x : command.qx) x += offset_x; } command.clip_x1 = clip_x1; command.clip_y1 = clip_y1; command.clip_x2 = clip_x2; command.clip_y2 = clip_y2; command.behind_3d = Render3DDraws().empty(); if (command.kind == UIRenderCommand::Bar && command.quad) { // Cooldown fans are triangles. Clip their geometry before either renderer consumes it. std::vector polygon{{command.qx[0], command.qy[0]}, {command.qx[1], command.qy[1]}, {command.qx[2], command.qy[2]}}; const float bounds[4] = {clip_x1, clip_x2, clip_y1, clip_y2}; for (int edge = 0; edge < 4; ++edge) polygon = clip_polygon(std::move(polygon), edge, bounds[edge]); for (std::size_t i = 1; i + 1 < polygon.size(); ++i) { UIRenderCommand piece = command; const Point tri[3] = {polygon[0], polygon[i], polygon[i + 1]}; const float area = (tri[1].x - tri[0].x) * (tri[2].y - tri[0].y) - (tri[1].y - tri[0].y) * (tri[2].x - tri[0].x); if (area == 0.0f) continue; for (int k = 0; k < 4; ++k) { piece.qx[k] = tri[k < 3 ? k : 2].x; piece.qy[k] = tri[k < 3 ? k : 2].y; } piece.x1 = std::min({tri[0].x, tri[1].x, tri[2].x}); piece.y1 = std::min({tri[0].y, tri[1].y, tri[2].y}); piece.x2 = std::max({tri[0].x, tri[1].x, tri[2].x}); piece.y2 = std::max({tri[0].y, tri[1].y, tri[2].y}); commands.push_back(std::move(piece)); } return; } commands.push_back(command); } const std::vector& UIRenderCommands() { return commands; } void UIRenderSetClip(float x, float y, float width, float height) { saved_x1 = clip_x1; saved_y1 = clip_y1; saved_x2 = clip_x2; saved_y2 = clip_y2; clip_x1 = x + offset_x; clip_y1 = y; clip_x2 = x + offset_x + width; clip_y2 = y + height; } void UIRenderRestoreClip() { clip_x1 = saved_x1; clip_y1 = saved_y1; clip_x2 = saved_x2; clip_y2 = saved_y2; } void UIRenderAddImage(const CGraphicTexture* texture, const float x[4], const float y[4], float su, float sv, float eu, float ev, std::uint32_t argb, int blend) { UIRenderAddImageNamed(UIRenderTextureName(texture), x, y, su, sv, eu, ev, argb, blend); } void UIRenderAddImageNamed(const std::string& texture_name, const float x[4], const float y[4], float su, float sv, float eu, float ev, std::uint32_t argb, int blend) { // 40250 draws the image quad through the device: the UI pass's world transform places it // (identity except where a caller sets one, e.g. CPythonMiniMap::RenderAtlas's window offset) and a // stage-0 MODULATE with D3DTA_TFACTOR tints it (the atlas's NPC/warp/waypoint marks). D3DXMATRIX world; D3DXMatrixIdentity(&world); if (CStateManager* state = CStateManager::InstancePtr()) { state->GetTransform(D3DTS_WORLD, &world); DWORD op = 0, arg1 = 0, arg2 = 0; state->GetTextureStageState(0, D3DTSS_COLOROP, &op); state->GetTextureStageState(0, D3DTSS_COLORARG1, &arg1); state->GetTextureStageState(0, D3DTSS_COLORARG2, &arg2); if (op == D3DTOP_MODULATE && (arg1 == D3DTA_TFACTOR || arg2 == D3DTA_TFACTOR)) { // TFACTOR x TEXTURE drops the vertex colour; TFACTOR x DIFFUSE scales it. The canvas // multiplies the texture in. const std::uint32_t factor = state->GetRenderState(D3DRS_TEXTUREFACTOR) & 0x00FFFFFFu; const DWORD other = arg1 == D3DTA_TFACTOR ? arg2 : arg1; if (other == D3DTA_DIFFUSE) { std::uint32_t tinted = argb & 0xFF000000u; for (int shift = 0; shift < 24; shift += 8) tinted |= ((((argb >> shift) & 255) * ((factor >> shift) & 255) + 127) / 255) << shift; argb = tinted; } else { argb = (argb & 0xFF000000u) | factor; } } } UIRenderCommand command{UIRenderCommand::Image, 0, 0, 0, 0, argb}; for (int i = 0; i < 4; ++i) { command.qx[i] = x[i] * world._11 + y[i] * world._21 + world._41 + 0.5f; command.qy[i] = x[i] * world._12 + y[i] * world._22 + world._42 + 0.5f; } command.x1 = std::min({command.qx[0], command.qx[1], command.qx[2], command.qx[3]}); command.y1 = std::min({command.qy[0], command.qy[1], command.qy[2], command.qy[3]}); command.x2 = std::max({command.qx[0], command.qx[1], command.qx[2], command.qx[3]}); command.y2 = std::max({command.qy[0], command.qy[1], command.qy[2], command.qy[3]}); command.text = texture_name; command.quad = true; command.su = su; command.sv = sv; command.eu = eu; command.ev = ev; command.blend = blend; UIRenderAdd(std::move(command)); }