22#include <glm/gtc/matrix_transform.hpp>
35constexpr float kPi = 3.14159265358979323846f;
37float degToRad(
float d) {
return d * kPi / 180.f; }
39std::string jsonEscape(
const std::string &
s) {
41 out.reserve(
s.size() + 8);
44 case '"': out +=
"\\\"";
break;
45 case '\\': out +=
"\\\\";
break;
46 case '\n': out +=
"\\n";
break;
47 case '\r': out +=
"\\r";
break;
48 case '\t': out +=
"\\t";
break;
55std::string num(
float v) {
57 std::snprintf(buf,
sizeof(buf),
"%g",
static_cast<double>(
v));
61std::string vec3Json(
const glm::vec3 &
v) {
62 return "[" + num(
v.x) +
"," + num(
v.y) +
"," + num(
v.z) +
"]";
67 Graphics *gfx() {
return eve::ModuleManager::getInstance<Graphics>(
"Graphics"); }
70 if (ecs::current()->getManager<Camera3D>() !=
nullptr) {
71 auto view = ecs::View<Camera3D, Camera3D::Data>();
72 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
82 if (
auto *g =
const_cast<RenderCaptureImpl *
>(
this)->gfx()) {
83 info.
width = g->getWidth();
84 info.
height = g->getHeight();
89 info.
backend = g->getBackendName();
94 void setReadbackEnabled(
bool enabled)
override {
95 if (
auto *g = gfx()) g->setScreenReadbackEnabled(
enabled);
98 bool savePng(
const std::string &path,
int *outWidth,
int *outHeight,
99 std::string *err)
override {
102 if (err) *err =
"Graphics module not available";
105 g->setScreenReadbackEnabled(
true);
106 image::ImageData *frame =
nullptr;
108 frame = g->newImageData();
110 if (err) *err =
"no presented frame";
114 if (err) *err =
"no presented frame (enable readback and render a frame first)";
117 filesystem::FileData *png =
nullptr;
119 png = frame->encode(image::ImageData::FormatHandler::ENCODED_PNG,
"frame.png",
false);
122 if (err) *err =
"png encode failed";
127 if (err) *err =
"png encode returned null";
131 std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
132 std::ofstream out(path, std::ios::binary);
136 if (err) *err =
"cannot open output file: " + path;
139 out.write(
static_cast<const char *
>(png->getData()),
140 static_cast<std::streamsize
>(png->getSize()));
141 const bool ok = out.good();
144 if (outWidth) *outWidth = g->getPixelWidth();
145 if (outHeight) *outHeight = g->getPixelHeight();
146 if (!
ok && err) *err =
"failed writing: " + path;
150 std::string capturePngDataUrl()
override {
153 g->setScreenReadbackEnabled(
true);
154 image::ImageData *frame =
nullptr;
156 frame = g->newImageData();
160 if (!frame)
return {};
161 filesystem::FileData *png =
nullptr;
163 png = frame->encode(image::ImageData::FormatHandler::ENCODED_PNG,
"frame.png",
false);
173 char *b64 =
eve::b64_encode(
static_cast<const char *
>(png->getData()), png->getSize(), 0, len);
174 std::string url =
"data:image/png;base64," + std::string(b64 ? b64 :
"", len);
181 bool ensureCamera()
override {
return camera() !=
nullptr; }
183 bool setCameraPose(
float ex,
float ey,
float ez,
float tx,
float ty,
float tz,
184 float fovYDeg)
override {
185 auto *cam = camera();
186 if (!cam)
return false;
187 cam->setActive(
true);
188 cam->setEye(ex, ey, ez);
189 cam->setTarget(tx, ty, tz);
190 if (fovYDeg > 0.f) cam->setFov(fovYDeg);
194 bool setCameraPoseYawPitch(
float ex,
float ey,
float ez,
float yawDeg,
float pitchDeg,
195 float fovYDeg)
override {
196 auto *cam = camera();
197 if (!cam)
return false;
198 cam->setActive(
true);
199 const float yaw = degToRad(yawDeg);
200 const float pitch = degToRad(pitchDeg);
201 const glm::vec3
dir(std::cos(pitch) * std::sin(yaw), std::sin(pitch),
202 std::cos(pitch) * std::cos(yaw));
203 cam->setEye(ex, ey, ez);
204 cam->setTarget(ex +
dir.x * 100.f, ey +
dir.y * 100.f, ez +
dir.z * 100.f);
205 if (fovYDeg > 0.f) cam->setFov(fovYDeg);
209 std::string cameraPoseJson()
override {
210 auto *cam = camera();
211 if (!cam)
return "{\"error\":\"no camera\"}";
212 auto d = cam->data();
213 std::string out =
"{\"eye\":" + vec3Json(glm::vec3(
d->eyeX,
d->eyeY,
d->eyeZ));
214 out +=
",\"target\":" + vec3Json(glm::vec3(
d->targetX,
d->targetY,
d->targetZ));
215 out +=
",\"up\":" + vec3Json(glm::vec3(
d->upX,
d->upY,
d->upZ));
216 out +=
",\"fov\":" + num(
d->fovYDeg);
217 out +=
",\"near\":" + num(
d->nearZ);
218 out +=
",\"far\":" + num(
d->farZ);
219 if (
auto *g = gfx()) {
220 out +=
",\"viewportWidth\":" + std::to_string(g->getPixelWidth());
221 out +=
",\"viewportHeight\":" + std::to_string(g->getPixelHeight());
227 std::string visibleEntitiesJson(
float fovYDeg,
bool *
ok)
override {
228 return visibleAt(
nullptr,
nullptr, fovYDeg,
ok);
231 std::string visibleEntitiesJsonAt(
float ex,
float ey,
float ez,
float tx,
float ty,
float tz,
232 float fovYDeg,
bool *
ok)
override {
233 const glm::vec3
eye(ex, ey, ez);
234 const glm::vec3 target(tx, ty, tz);
235 return visibleAt(&
eye, &target, fovYDeg,
ok);
238 std::string entityIdMaskJson(
int *outWidth,
int *outHeight,
bool *
ok)
override {
241 if (!g)
return "{\"error\":\"Graphics module not available\"}";
242 auto *cam = camera();
243 if (!cam)
return "{\"error\":\"no camera\"}";
244 auto d = cam->data();
245 const glm::vec3
eye(
d->eyeX,
d->eyeY,
d->eyeZ);
246 const glm::vec3 target(
d->targetX,
d->targetY,
d->targetZ);
247 const glm::vec3 up(
d->upX,
d->upY,
d->upZ);
248 const int vw = g->getPixelWidth() > 0 ? g->getPixelWidth() : g->getWidth();
249 const int vh = g->getPixelHeight() > 0 ? g->getPixelHeight() : g->getHeight();
250 if (outWidth) *outWidth = vw;
251 if (outHeight) *outHeight = vh;
252 if (vw <= 0 || vh <= 0)
return "{\"error\":\"empty viewport\"}";
253 const glm::mat4 viewM = glm::lookAtRH(
eye, target, up);
256 const glm::mat4
viewProj = projM * viewM;
258 std::vector<Graphics::EntityIdDraw> draws;
260 std::string entities;
262 if (ecs::current()->getManager<scene::SceneHost>() !=
nullptr) {
263 auto hostView = ecs::View<scene::SceneHost, scene::SceneHost::Meta,
264 scene::SceneHost::Tree>();
265 for (
auto it = hostView.begin(); it != hostView.end(); ++it) {
266 auto [meta, tree] = *it;
267 if (!meta || !meta->entity || !tree)
continue;
268 auto *host = meta->entity;
269 host->walkDepthFirst([&](scene::SceneHost *,
int, scene::SceneNode &node) {
272 if (!l || !l->target)
return;
273 auto *r =
static_cast<Renderable3D *
>(l->target);
274 auto mr = r->meshRenderer();
276 if (!
mesh || !
mesh->gpuHandle)
return;
277 Graphics::EntityIdDraw draw;
279 draw.model =
node.world;
280 const uint32_t packed = uint32_t(
id);
281 draw.idColor = glm::vec4((packed & 255u) / 255.f,
282 ((packed >> 8) & 255u) / 255.f,
283 ((packed >> 16) & 255u) / 255.f, 1.f);
284 draws.push_back(draw);
285 if (!entities.empty()) entities +=
",";
286 entities +=
"{\"id\":" + std::to_string(
id) +
293 image::ImageData *img = g->renderEntityIdMask(draws,
viewProj, vw, vh);
294 if (!img)
return "{\"error\":\"id mask unsupported\"}";
297 return "{\"camera\":" + vec3Json(
eye) +
",\"viewportWidth\":" + std::to_string(vw) +
298 ",\"viewportHeight\":" + std::to_string(vh) +
",\"entities\":[" + entities +
299 "],\"count\":" + std::to_string(
id - 1) +
"}";
302 bool entityIdMaskPng(
const std::string &path, std::string *err)
override {
305 if (err) *err =
"Graphics module not available";
308 auto *cam = camera();
310 if (err) *err =
"no camera";
313 auto d = cam->data();
314 const glm::vec3
eye(
d->eyeX,
d->eyeY,
d->eyeZ);
315 const glm::vec3 target(
d->targetX,
d->targetY,
d->targetZ);
316 const glm::vec3 up(
d->upX,
d->upY,
d->upZ);
317 const int vw = g->getPixelWidth() > 0 ? g->getPixelWidth() : g->getWidth();
318 const int vh = g->getPixelHeight() > 0 ? g->getPixelHeight() : g->getHeight();
319 if (vw <= 0 || vh <= 0) {
320 if (err) *err =
"empty viewport";
323 const glm::mat4 viewM = glm::lookAtRH(
eye, target, up);
326 const glm::mat4
viewProj = projM * viewM;
327 std::vector<Graphics::EntityIdDraw> draws;
330 if (ecs::current()->getManager<scene::SceneHost>() !=
nullptr) {
331 auto hostView = ecs::View<scene::SceneHost, scene::SceneHost::Meta,
332 scene::SceneHost::Tree>();
333 for (
auto it = hostView.begin(); it != hostView.end(); ++it) {
334 auto [meta, tree] = *it;
335 if (!meta || !meta->entity || !tree)
continue;
336 auto *host = meta->entity;
337 host->walkDepthFirst([&](scene::SceneHost *,
int, scene::SceneNode &node) {
340 if (!l || !l->target)
return;
341 auto *r =
static_cast<Renderable3D *
>(l->target);
342 auto mr = r->meshRenderer();
344 if (!
mesh || !
mesh->gpuHandle)
return;
345 Graphics::EntityIdDraw draw;
347 draw.model =
node.world;
348 const uint32_t packed = uint32_t(
id);
349 draw.idColor = glm::vec4((packed & 255u) / 255.f,
350 ((packed >> 8) & 255u) / 255.f,
351 ((packed >> 16) & 255u) / 255.f, 1.f);
352 draws.push_back(draw);
357 image::ImageData *img = g->renderEntityIdMask(draws,
viewProj, vw, vh);
359 if (err) *err =
"id mask unsupported";
362 filesystem::FileData *png =
nullptr;
364 png = img->encode(image::ImageData::FormatHandler::ENCODED_PNG,
"id.png",
false);
367 if (err) *err =
"png encode failed";
373 std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
374 std::ofstream out(path, std::ios::binary);
376 out.write(
static_cast<const char *
>(png->getData()),
377 static_cast<std::streamsize
>(png->getSize()));
383 if (!
ok && err) *err =
"failed writing id mask";
387 bool gbufferPng(
const std::string &
name,
const std::string &path, std::string *err)
override {
390 if (err) *err =
"Graphics module not available";
393 image::ImageData *img = g->readGBufferToImageData(
name);
395 if (err) *err =
"G-buffer attachment unavailable: " +
name;
398 filesystem::FileData *png =
nullptr;
400 png = img->encode(image::ImageData::FormatHandler::ENCODED_PNG,
"buf.png",
false);
403 if (err) *err =
"png encode failed";
406 const bool ok = png !=
nullptr;
409 std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
410 std::ofstream out(path, std::ios::binary);
412 out.write(
static_cast<const char *
>(png->getData()),
413 static_cast<std::streamsize
>(png->getSize()));
418 if (!
ok && err) *err =
"png encode returned null";
423 std::string visibleAt(
const glm::vec3 *
eye,
const glm::vec3 *target,
float fovYDeg,
427 auto *cam = camera();
429 glm::vec3 camEye(0.f, 1.8f, 3.f);
430 glm::vec3 camTarget(0.f, 1.2f, 0.f);
432 float camNear = 0.1f, camFar = 100.f;
434 auto d = cam->data();
435 camEye = glm::vec3(
d->eyeX,
d->eyeY,
d->eyeZ);
436 camTarget = glm::vec3(
d->targetX,
d->targetY,
d->targetZ);
445 if (fovYDeg > 0.f) camFov = fovYDeg;
447 const int vw = g ? g->getPixelWidth() : 1280;
448 const int vh = g ? g->getPixelHeight() : 720;
449 const glm::vec3 up(0.f, 1.f, 0.f);
450 const glm::mat4
view = glm::lookAtRH(camEye, camTarget, up);
455 static const glm::vec3 kLocalCorners[8] = {
456 {-0.5f, -0.5f, -0.5f}, {0.5f, -0.5f, -0.5f}, {-0.5f, 0.5f, -0.5f},
457 {-0.5f, -0.5f, 0.5f}, {0.5f, 0.5f, -0.5f}, {0.5f, -0.5f, 0.5f},
458 {-0.5f, 0.5f, 0.5f}, {0.5f, 0.5f, 0.5f}};
460 std::string entities;
463 if (ecs::current()->getManager<scene::SceneHost>() !=
nullptr) {
464 auto hostView = ecs::View<scene::SceneHost, scene::SceneHost::Meta,
465 scene::SceneHost::Tree>();
466 for (
auto it = hostView.begin(); it != hostView.end(); ++it) {
467 auto [meta, tree] = *it;
468 if (!meta || !meta->entity || !tree)
continue;
469 auto *host = meta->entity;
470 host->walkDepthFirst([&](scene::SceneHost *,
int, scene::SceneNode &node) {
472 float minX = 1e30f, minY = 1e30f, minZ = 1e30f;
473 float maxX = -1e30f, maxY = -1e30f, maxZ = -1e30f;
474 float scMinX = 1e30f, scMinY = 1e30f;
475 float scMaxX = -1e30f, scMaxY = -1e30f;
476 bool anyInFront =
false;
477 for (
const auto &
c : kLocalCorners) {
478 const glm::vec4
w =
node.world * glm::vec4(
c, 1.f);
479 minX = std::min(minX,
w.x);
480 minY = std::min(minY,
w.y);
481 minZ = std::min(minZ,
w.z);
482 maxX = std::max(maxX,
w.x);
483 maxY = std::max(maxY,
w.y);
484 maxZ = std::max(maxZ,
w.z);
486 if (clip.w <= 1e-6f)
continue;
488 const float ndcX = clip.x / clip.w;
489 const float ndcY = clip.y / clip.w;
490 const float sx = (ndcX * 0.5f + 0.5f) *
float(vw);
491 const float sy = (ndcY * 0.5f + 0.5f) *
float(vh);
492 scMinX = std::min(scMinX, sx);
493 scMinY = std::min(scMinY, sy);
494 scMaxX = std::max(scMaxX, sx);
495 scMaxY = std::max(scMaxY, sy);
497 if (!anyInFront)
return;
498 if (scMaxX < 0.f || scMinX >
float(vw) || scMaxY < 0.f ||
501 if (!entities.empty()) entities +=
",";
505 "\",\"world_aabb\":{\"min\":" + vec3Json(glm::vec3(minX, minY, minZ)) +
506 ",\"max\":" + vec3Json(glm::vec3(maxX, maxY, maxZ)) +
507 "},\"screen_bbox\":{\"x\":" + num(scMinX) +
",\"y\":" + num(scMinY) +
508 ",\"w\":" + num(scMaxX - scMinX) +
",\"h\":" + num(scMaxY - scMinY) +
515 return "{\"camera\":" + vec3Json(camEye) +
",\"cameraTarget\":" + vec3Json(camTarget) +
516 ",\"fov\":" + num(camFov) +
",\"viewportWidth\":" + std::to_string(vw) +
517 ",\"viewportHeight\":" + std::to_string(vh) +
",\"entities\":[" + entities +
518 "],\"count\":" + std::to_string(count) +
"}";
525 static RenderCaptureImpl
impl;
Renderable3D::MeshRenderer * mr
Frame capture + camera + visible-entity inspection (graphics).
static Camera3D * createCamera()
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
glm::mat4 perspectiveVulkanRH_ZO(float fovyRad, float aspect, float zNear, float zFar)
Right-handed, zero-to-one depth perspective for Vulkan swapchains.
void registerGraphicsCapabilities()
NodeDesc node(std::string id, std::vector< NodeDesc > children, std::string name)
int findLinkKind(const char *kind)
char * b64_encode(const char *src, size_t srclen, size_t linelen, size_t &dstlen)
Base64-encode data.
Render surface / frame state snapshot (value type).
std::string space
"2d" or "3d" (string enum per module convention).