Files
ReSkate/Extension/UI/Overlay/xray_overlay.cpp
T
AyeZeeBB db52508bdb Plugins: Hall of Meat and grind exits are plugin mods now; their C++ is gone
Hall of Meat (contrib/plugins/hall-of-meat) and grind exit triggers
(contrib/plugins/grind-exits) are Lua plugins: the bail model, its Meat card,
each map's best, the trigger rules, settings, console commands and menu panels
are theirs, with their own checks that dingosdk_plugin_tests runs.

What has to run in the game stays in core as plugin services:
- every physics step of the skater (skater_physics, the physics_step event,
  permission events.physics);
- the skater's x-ray (xray, xray_render, xray_skeleton; reskate.skater.xray and
  the draw handler's d:skeleton);
- skate.'s bottom left HUD corner (hud_corner; reskate.hud.corner);
- the game's slow motion, never in a session (game_speed; reskate.time.slow and
  speed, permission game.speed);
- the side of the next pop out of a grind and pad controls kept from the game
  (grind_exit; reskate.skater.grind_exit, the grind_exit event, reskate.input.hide);
- skate.'s own pictures by name (game_images; d:game_image), d:gradient and a
  font for drawn text.

Removed: Extension/HallOfMeat, grind_exit_control and its commands, the HALL OF
MEAT and GRIND EXITS add-on pages, home tile, hub and native menu rows, their
menu models, and the hallofmeat and grindexit built-in commands.
2026-10-19 16:51:28 -05:00

80 lines
4.3 KiB
C++

#include "xray_overlay.h"
#include "Extension/Skater/xray.h"
#include <algorithm>
#include <cmath>
#include <utility>
#include <vector>
namespace dingosdk::overlay {
namespace {
// `colour` darkened to `light` (0 black, 1 itself), at `alpha`.
ImU32 lit(ImU32 colour, float light, float alpha) {
const auto channel = [&](int shift) {
return static_cast<ImU32>(static_cast<float>((colour >> shift) & 0xff) * std::clamp(light, 0.0f, 1.0f)) << shift;
};
const auto a = static_cast<ImU32>(255.0f * std::clamp(alpha, 0.0f, 1.0f));
return channel(IM_COL32_R_SHIFT) | channel(IM_COL32_G_SHIFT) | channel(IM_COL32_B_SHIFT) | a << IM_COL32_A_SHIFT;
}
}
// The bodies a plugin painted, as an x-ray over the world projected like the nametags; the rest are
// not drawn. Every triangle of a painted body is drawn back to front, each vertex lit by how it faces
// the camera: an outline near solid and a middle fainter, so the bones read through each other and
// against the world. A triangle reaching into an unpainted body fades out.
void draw_xray(ImDrawList* draw, const std::array<ImU32, skater_body::count>& paint, float alpha) {
static xray::Pose skeleton; // render thread only: reused frame to frame
if (alpha <= 0 || !xray::latest(skeleton)) return;
using Vec3 = std::array<float, 3>;
const auto dot = [](const Vec3& a, const Vec3& b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; };
const auto& positions = skeleton.positions;
const auto count = positions.size();
const auto* mesh = skeleton.mesh.get();
if (!count || count > 0xffff || !mesh || mesh->parts.size() != count || skeleton.normals.size() != count ||
!(skeleton.vertical_fov > 1 && skeleton.vertical_fov < 175))
return;
constexpr float solid = 0.6f;
const auto display = ImGui::GetIO().DisplaySize;
const auto& m = skeleton.camera;
const Vec3 right{m[0], m[1], m[2]}, up{m[4], m[5], m[6]}, back{m[8], m[9], m[10]}, origin{m[12], m[13], m[14]};
const float focal = display.y / (2.0f * std::tan(skeleton.vertical_fov * 3.14159265f / 360.0f));
const ImVec2 centre(display.x * 0.5f, display.y * 0.5f);
// Render thread only: reused frame to frame.
static std::vector<ImVec2> at;
static std::vector<float> depth;
static std::vector<ImU32> colour; // 0 for an unpainted body's vertex
static std::vector<std::pair<float, std::uint32_t>> order; // a triangle's depth, its first index
at.resize(count);
depth.resize(count);
colour.resize(count);
for (std::size_t i = 0; i < count; ++i) {
const auto& p = positions[i];
const Vec3 delta{p[0] - origin[0], p[1] - origin[1], p[2] - origin[2]};
depth[i] = -dot(delta, back);
const float z = std::max(depth[i], 0.1f);
at[i] = ImVec2(centre.x + dot(delta, right) * focal / z, centre.y - dot(delta, up) * focal / z);
const float distance = std::sqrt(dot(delta, delta));
const float facing = distance > 1e-4f ? std::abs(dot(skeleton.normals[i], delta)) / distance : 1.0f;
const auto painted = paint[std::min<std::size_t>(mesh->parts[i], skater_body::count - 1)];
const float faint = 0.25f + 0.6f * (1.0f - facing);
colour[i] = painted ? lit(painted, 0.55f + 0.45f * facing, alpha * (solid + (1.0f - solid) * faint)) : 0;
}
const auto& triangles = mesh->triangles;
order.clear();
for (std::uint32_t t = 0; t + 2 < triangles.size(); t += 3) {
const auto a = triangles[t], b = triangles[t + 1], c = triangles[t + 2];
if (a >= count || b >= count || c >= count || depth[a] <= 0.1f || depth[b] <= 0.1f || depth[c] <= 0.1f ||
(!colour[a] && !colour[b] && !colour[c]))
continue;
order.emplace_back(depth[a] + depth[b] + depth[c], t);
}
if (order.empty()) return;
std::sort(order.begin(), order.end(), [](const auto& x, const auto& y) { return x.first > y.first; }); // far first
const auto uv = ImGui::GetFontTexUvWhitePixel();
draw->PrimReserve(static_cast<int>(order.size() * 3), static_cast<int>(count));
const auto first = draw->_VtxCurrentIdx;
for (std::size_t i = 0; i < count; ++i) draw->PrimWriteVtx(at[i], uv, colour[i]);
for (const auto& [unused, t] : order)
for (std::uint32_t corner = 0; corner < 3; ++corner) draw->PrimWriteIdx(static_cast<ImDrawIdx>(first + triangles[t + corner]));
}
}