#include "../Engine/Core/engine.h" #include "../Engine/Core/logger.h" #include "../Engine/Core/asset_manager.h" #include "../Engine/Rendering/graphics_device.h" #include "../Engine/Rendering/renderer.h" #include "../Engine/Rendering/shader.h" #include "../Engine/Rendering/material.h" #include "../Engine/Rendering/mesh.h" #include "../Engine/Rendering/camera.h" #include "../Engine/Scene/scene_graph.h" #include "../Engine/Scene/scene_render.h" #include "../Engine/Scene/scene_serialize.h" #include "../Engine/Editor/glyph_atlas.h" #include "../Engine/include/engine_api.h" #include "../Engine/ASM/math_c_fallback.h" #include #include #include #include #include /* -------------------------------------------------------------------------- */ /* Editor state */ /* -------------------------------------------------------------------------- */ static Engine* g_engine = NULL; static GraphicsDevice* g_gdev = NULL; static Renderer* g_renderer = NULL; static AssetManager* g_assets = NULL; static Asset* g_shader_asset = NULL; static Scene* g_scene = NULL; static Camera g_camera; static GlyphAtlas g_atlas; static int g_atlas_ok = 0; static SceneNode* g_selected = NULL; static int g_mouse_x = 0; static int g_mouse_y = 0; static int g_mouse_down[3] = {0}; static int g_mouse_last_x = 0; static int g_mouse_last_y = 0; static int g_camera_drag = 0; static char g_console[16][256]; static int g_console_count = 0; static char g_status[256] = ""; /* UI metrics */ #define ED_MENU_H 30 #define ED_TOOLBAR_H 30 #define ED_LEFT_W 240 #define ED_RIGHT_W 260 #define ED_BOTTOM_H 180 #define ED_SPLITTER 1 /* UI D3D resources */ static ID3D11DepthStencilState* g_ui_depth = NULL; static ID3D11RasterizerState* g_ui_raster = NULL; static ID3D11BlendState* g_ui_blend = NULL; static ID3D11SamplerState* g_ui_sampler = NULL; static ID3D11Buffer* g_ui_cb = NULL; static ID3D11VertexShader* g_ui_vs = NULL; static ID3D11PixelShader* g_ui_ps = NULL; static ID3D11InputLayout* g_ui_layout = NULL; static ID3D11VertexShader* g_text_vs = NULL; static ID3D11PixelShader* g_text_ps = NULL; static ID3D11InputLayout* g_text_layout = NULL; typedef struct { float p[3], n[3], uv[2], c[4]; } UIVertex; /* -------------------------------------------------------------------------- */ /* Console */ /* -------------------------------------------------------------------------- */ static void console_add(const char* msg) { if (g_console_count >= 16) { for (int i = 0; i < 15; i++) strcpy(g_console[i], g_console[i+1]); g_console_count = 15; } strncpy(g_console[g_console_count], msg, 255); g_console[g_console_count][255] = 0; g_console_count++; } /* -------------------------------------------------------------------------- */ /* Shader helpers */ /* -------------------------------------------------------------------------- */ static ID3DBlob* compile_blob(const char* code, const char* target) { ID3DBlob *blob = NULL, *err = NULL; D3DCompile(code, strlen(code), NULL, NULL, NULL, "main", target, D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_PACK_MATRIX_ROW_MAJOR, 0, &blob, &err); if (err) { LOG_ERROR("Editor shader compile (%s): %s", target, (char*)err->lpVtbl->GetBufferPointer(err)); err->lpVtbl->Release(err); } return blob; } static int compile_ui_shaders(ID3D11Device* dev) { const char* vs = "cbuffer CB:register(b0){float4x4 WVP;};" "struct I{float4 p:POSITION;float3 n:NORMAL;float2 u:TEXCOORD;float4 c:COLOR;};" "struct O{float4 p:SV_POSITION;float2 u:TEXCOORD;float4 c:COLOR;};" "O main(I i){O o;o.p=mul(i.p,WVP);o.u=i.u;o.c=i.c;return o;}"; const char* ps = "struct O{float4 p:SV_POSITION;float2 u:TEXCOORD;float4 c:COLOR;};" "float4 main(O i):SV_TARGET{return i.c;}"; const char* tps = "Texture2D t:register(t0);SamplerState s:register(s0);" "struct O{float4 p:SV_POSITION;float2 u:TEXCOORD;float4 c:COLOR;};" "float4 main(O i):SV_TARGET{float a=t.Sample(s,i.u).a;return float4(i.c.rgb,i.c.a*a);}"; ID3DBlob *b1 = compile_blob(vs, "vs_5_0"), *b2 = compile_blob(ps, "ps_5_0"), *b3 = compile_blob(tps, "ps_5_0"); if (!b1 || !b2 || !b3) return 0; dev->lpVtbl->CreateVertexShader(dev, b1->lpVtbl->GetBufferPointer(b1), b1->lpVtbl->GetBufferSize(b1), NULL, &g_ui_vs); dev->lpVtbl->CreatePixelShader(dev, b2->lpVtbl->GetBufferPointer(b2), b2->lpVtbl->GetBufferSize(b2), NULL, &g_ui_ps); dev->lpVtbl->CreatePixelShader(dev, b3->lpVtbl->GetBufferPointer(b3), b3->lpVtbl->GetBufferSize(b3), NULL, &g_text_ps); D3D11_INPUT_ELEMENT_DESC layout[] = { {"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0}, {"NORMAL",0,DXGI_FORMAT_R32G32B32_FLOAT,0,12,D3D11_INPUT_PER_VERTEX_DATA,0}, {"TEXCOORD",0,DXGI_FORMAT_R32G32_FLOAT,0,24,D3D11_INPUT_PER_VERTEX_DATA,0}, {"COLOR",0,DXGI_FORMAT_R32G32B32A32_FLOAT,0,32,D3D11_INPUT_PER_VERTEX_DATA,0}, }; dev->lpVtbl->CreateInputLayout(dev, layout, 4, b1->lpVtbl->GetBufferPointer(b1), b1->lpVtbl->GetBufferSize(b1), &g_ui_layout); g_text_layout = g_ui_layout; g_text_vs = g_ui_vs; b1->lpVtbl->Release(b1); b2->lpVtbl->Release(b2); b3->lpVtbl->Release(b3); return g_ui_vs && g_ui_ps && g_text_ps && g_ui_layout; } static void create_ui_states(ID3D11Device* dev) { D3D11_DEPTH_STENCIL_DESC dsd = {0}; dsd.DepthEnable = FALSE; dsd.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; dev->lpVtbl->CreateDepthStencilState(dev, &dsd, &g_ui_depth); D3D11_RASTERIZER_DESC rd = {0}; rd.FillMode = D3D11_FILL_SOLID; rd.CullMode = D3D11_CULL_NONE; dev->lpVtbl->CreateRasterizerState(dev, &rd, &g_ui_raster); D3D11_BLEND_DESC bd = {0}; bd.RenderTarget[0].BlendEnable = TRUE; bd.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; bd.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; bd.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; bd.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; bd.RenderTarget[0].BlendOp = bd.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; bd.RenderTarget[0].RenderTargetWriteMask = 0x0F; dev->lpVtbl->CreateBlendState(dev, &bd, &g_ui_blend); D3D11_SAMPLER_DESC sd = {0}; sd.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; sd.AddressU = sd.AddressV = sd.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; dev->lpVtbl->CreateSamplerState(dev, &sd, &g_ui_sampler); D3D11_BUFFER_DESC cbd = {0}; cbd.ByteWidth = 256; cbd.Usage = D3D11_USAGE_DEFAULT; cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; dev->lpVtbl->CreateBuffer(dev, &cbd, NULL, &g_ui_cb); } /* -------------------------------------------------------------------------- */ /* UI rendering */ /* -------------------------------------------------------------------------- */ static void ui_set_projection(ID3D11DeviceContext* ctx, float sw, float sh) { float proj[16] = {0}; proj[0] = 2.0f / sw; proj[5] = -2.0f / sh; proj[10] = 1.0f; proj[12] = -1.0f; proj[13] = 1.0f; proj[15] = 1.0f; ctx->lpVtbl->UpdateSubresource(ctx, (ID3D11Resource*)g_ui_cb, 0, NULL, proj, 0, 0); } static void ui_setup_solid(ID3D11DeviceContext* ctx) { ctx->lpVtbl->VSSetShader(ctx, g_ui_vs, NULL, 0); ctx->lpVtbl->PSSetShader(ctx, g_ui_ps, NULL, 0); ctx->lpVtbl->IASetInputLayout(ctx, g_ui_layout); ctx->lpVtbl->VSSetConstantBuffers(ctx, 0, 1, &g_ui_cb); ctx->lpVtbl->OMSetBlendState(ctx, NULL, NULL, 0xFFFFFFFF); } static void ui_setup_text(ID3D11DeviceContext* ctx) { ctx->lpVtbl->VSSetShader(ctx, g_text_vs, NULL, 0); ctx->lpVtbl->PSSetShader(ctx, g_text_ps, NULL, 0); ctx->lpVtbl->IASetInputLayout(ctx, g_text_layout); ctx->lpVtbl->VSSetConstantBuffers(ctx, 0, 1, &g_ui_cb); ctx->lpVtbl->PSSetShaderResources(ctx, 0, 1, &g_atlas.srv); ctx->lpVtbl->PSSetSamplers(ctx, 0, 1, &g_ui_sampler); ctx->lpVtbl->OMSetBlendState(ctx, g_ui_blend, NULL, 0xFFFFFFFF); } static void ui_begin(ID3D11DeviceContext* ctx, float sw, float sh) { D3D11_VIEWPORT vp = {0}; vp.Width = sw; vp.Height = sh; vp.MaxDepth = 1.0f; ctx->lpVtbl->RSSetViewports(ctx, 1, &vp); ctx->lpVtbl->RSSetState(ctx, g_ui_raster); ctx->lpVtbl->OMSetDepthStencilState(ctx, g_ui_depth, 0); ctx->lpVtbl->IASetPrimitiveTopology(ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); ui_set_projection(ctx, sw, sh); ui_setup_solid(ctx); } static void draw_quad(ID3D11DeviceContext* ctx, float x, float y, float w, float h, float r, float g, float b, float a) { UIVertex v[4] = { {{x, y, 0},{0,0,-1},{0,0},{r,g,b,a}}, {{x+w, y, 0},{0,0,-1},{1,0},{r,g,b,a}}, {{x+w, y+h, 0},{0,0,-1},{1,1},{r,g,b,a}}, {{x, y+h, 0},{0,0,-1},{0,1},{r,g,b,a}}, }; uint32_t idx[6] = {0,1,2, 0,2,3}; ID3D11Buffer *vb = NULL, *ib = NULL; D3D11_BUFFER_DESC bd = {0}; D3D11_SUBRESOURCE_DATA sd = {0}; bd.ByteWidth = sizeof(UIVertex)*4; bd.Usage = D3D11_USAGE_DEFAULT; bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; sd.pSysMem = v; g_gdev->device->lpVtbl->CreateBuffer(g_gdev->device, &bd, &sd, &vb); bd.ByteWidth = sizeof(uint32_t)*6; bd.BindFlags = D3D11_BIND_INDEX_BUFFER; sd.pSysMem = idx; g_gdev->device->lpVtbl->CreateBuffer(g_gdev->device, &bd, &sd, &ib); UINT stride = sizeof(UIVertex), offset = 0; ctx->lpVtbl->IASetVertexBuffers(ctx, 0, 1, &vb, &stride, &offset); ctx->lpVtbl->IASetIndexBuffer(ctx, ib, DXGI_FORMAT_R32_UINT, 0); ctx->lpVtbl->DrawIndexed(ctx, 6, 0, 0); if (ib) ib->lpVtbl->Release(ib); if (vb) vb->lpVtbl->Release(vb); } static void draw_text(ID3D11DeviceContext* ctx, const wchar_t* text, int x, int y, float r, float g, float b, float a) { if (!g_atlas_ok) return; ui_setup_text(ctx); for (const wchar_t* p = text; *p; p++) { float u0, v0, u1, v1, ax, ay; if (!glyph_atlas_get_or_create(&g_atlas, g_gdev->device, ctx, *p, &u0, &v0, &u1, &v1, &ax, &ay)) continue; UIVertex tv[4] = { {{(float)x, (float)y, 0},{0,0,1},{u0,v0},{r,g,b,a}}, {{(float)x + ax, (float)y, 0},{0,0,1},{u1,v0},{r,g,b,a}}, {{(float)x + ax, (float)y + ay, 0},{0,0,1},{u1,v1},{r,g,b,a}}, {{(float)x, (float)y + ay, 0},{0,0,1},{u0,v1},{r,g,b,a}}, }; uint32_t ti[6] = {0,1,2, 0,2,3}; ID3D11Buffer *tvb = NULL, *tib = NULL; D3D11_BUFFER_DESC bd = {0}; D3D11_SUBRESOURCE_DATA sd = {0}; bd.ByteWidth = sizeof(UIVertex)*4; bd.Usage = D3D11_USAGE_DEFAULT; bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; sd.pSysMem = tv; g_gdev->device->lpVtbl->CreateBuffer(g_gdev->device, &bd, &sd, &tvb); bd.ByteWidth = sizeof(uint32_t)*6; bd.BindFlags = D3D11_BIND_INDEX_BUFFER; sd.pSysMem = ti; g_gdev->device->lpVtbl->CreateBuffer(g_gdev->device, &bd, &sd, &tib); UINT stride = sizeof(UIVertex), offset = 0; ctx->lpVtbl->IASetVertexBuffers(ctx, 0, 1, &tvb, &stride, &offset); ctx->lpVtbl->IASetIndexBuffer(ctx, tib, DXGI_FORMAT_R32_UINT, 0); ctx->lpVtbl->DrawIndexed(ctx, 6, 0, 0); if (tib) tib->lpVtbl->Release(tib); if (tvb) tvb->lpVtbl->Release(tvb); x += (int)ax + 1; } ui_setup_solid(ctx); } static void draw_text_a(ID3D11DeviceContext* ctx, const char* text, int x, int y, float r, float g, float b, float a) { wchar_t buf[256]; int n = (int)strlen(text); if (n > 255) n = 255; for (int i = 0; i < n; i++) buf[i] = (wchar_t)text[i]; buf[n] = 0; draw_text(ctx, buf, x, y, r, g, b, a); } /* -------------------------------------------------------------------------- */ /* Scene helpers */ /* -------------------------------------------------------------------------- */ static SceneNode* create_primitive(Scene* scene, SceneNode* parent, AssetManager* am, ID3D11Device* dev, const char* name, MeshType type, Vec3 pos, Vec3 rot, Vec3 scale, Material* mat) { SceneNode* node = scene_create_node(scene, name); if (!node) return NULL; if (parent) scene_set_parent(node, parent); node->position = pos; node->scale = scale; quat_from_euler(rot.x, rot.y, rot.z, &node->rotation); Asset* mesh_asset = asset_manager_load_mesh(am, dev, name); if (!mesh_asset) { const char* builtin = NULL; if (type == MESH_CUBE) builtin = "cube"; else if (type == MESH_SPHERE) builtin = "sphere"; else if (type == MESH_PLANE) builtin = "plane"; if (builtin) mesh_asset = asset_manager_load_mesh(am, dev, builtin); } if (mesh_asset && mat) { MeshRenderer* mr = mesh_renderer_create(type, (Mesh*)asset_data(mesh_asset), mat); scene_node_set_mesh_renderer(node, mr); } return node; } static Scene* create_default_editor_scene(AssetManager* am, ID3D11Device* dev, Shader* shader) { Scene* scene = (Scene*)malloc(sizeof(Scene)); if (!scene) return NULL; scene_init(scene); strncpy(scene->root->name, "root", sizeof(scene->root->name) - 1); Material* mat_red = material_create(shader, NULL); mat_red->color = (Vec4){1.0f, 0.3f, 0.2f, 1.0f}; Material* mat_blue = material_create(shader, NULL); mat_blue->color = (Vec4){0.2f, 0.5f, 1.0f, 1.0f}; Material* mat_ground = material_create(shader, NULL); mat_ground->color = (Vec4){0.3f, 0.3f, 0.35f, 1.0f}; create_primitive(scene, scene->root, am, dev, "cube", MESH_CUBE, (Vec3){0,0,0}, (Vec3){0,0,0}, (Vec3){1,1,1}, mat_red); create_primitive(scene, scene->root, am, dev, "sphere", MESH_SPHERE, (Vec3){2.0f,0,0}, (Vec3){0,0,0}, (Vec3){0.8f,0.8f,0.8f}, mat_blue); create_primitive(scene, scene->root, am, dev, "ground", MESH_PLANE, (Vec3){0,-1.0f,0}, (Vec3){0,0,0}, (Vec3){1,1,1}, mat_ground); scene_update(scene); return scene; } /* -------------------------------------------------------------------------- */ /* Editor UI */ /* -------------------------------------------------------------------------- */ static void draw_panel_bg(ID3D11DeviceContext* ctx, float sw, float sh) { float body_h = sh - ED_MENU_H - ED_TOOLBAR_H - ED_BOTTOM_H; /* menu bar */ draw_quad(ctx, 0, 0, sw, ED_MENU_H, 0.14f, 0.14f, 0.18f, 1.0f); /* toolbar */ draw_quad(ctx, 0, ED_MENU_H, sw, ED_TOOLBAR_H, 0.20f, 0.20f, 0.26f, 1.0f); /* separators */ draw_quad(ctx, 0, ED_MENU_H, sw, 1, 0.30f, 0.45f, 0.75f, 1.0f); draw_quad(ctx, 0, ED_MENU_H + ED_TOOLBAR_H, sw, 1, 0.30f, 0.45f, 0.75f, 1.0f); /* left panel */ draw_quad(ctx, 0, ED_MENU_H + ED_TOOLBAR_H + 1, ED_LEFT_W, body_h, 0.18f, 0.18f, 0.23f, 1.0f); draw_quad(ctx, ED_LEFT_W, ED_MENU_H + ED_TOOLBAR_H + 1, ED_SPLITTER, body_h, 0.30f, 0.45f, 0.75f, 1.0f); /* right panel */ draw_quad(ctx, sw - ED_RIGHT_W, ED_MENU_H + ED_TOOLBAR_H + 1, ED_RIGHT_W, body_h, 0.18f, 0.18f, 0.23f, 1.0f); draw_quad(ctx, sw - ED_RIGHT_W - ED_SPLITTER, ED_MENU_H + ED_TOOLBAR_H + 1, ED_SPLITTER, body_h, 0.30f, 0.45f, 0.75f, 1.0f); /* bottom console */ draw_quad(ctx, 0, sh - ED_BOTTOM_H - ED_SPLITTER, sw, ED_SPLITTER, 0.30f, 0.45f, 0.75f, 1.0f); draw_quad(ctx, 0, sh - ED_BOTTOM_H, sw, ED_BOTTOM_H, 0.16f, 0.16f, 0.21f, 1.0f); /* status bar */ draw_quad(ctx, 0, sh - 22, sw, 22, 0.20f, 0.22f, 0.32f, 1.0f); } static void draw_hierarchy(ID3D11DeviceContext* ctx, float sh) { (void)sh; int y = ED_MENU_H + ED_TOOLBAR_H + 28; draw_text(ctx, L"\u573a\u666f\u5c42\u7ea7", 12, y - 20, 0.5f, 0.75f, 1.0f, 1.0f); if (!g_scene) return; for (int i = 1; i < g_scene->node_count; i++) { SceneNode* node = &g_scene->nodes[i]; if (node->id < 0) continue; int row_y = y + (i - 1) * 22; if (node == g_selected) draw_quad(ctx, 4, (float)row_y - 2, ED_LEFT_W - 8, 22, 0.25f, 0.38f, 0.65f, 1.0f); wchar_t wn[64]; int len = (int)strlen(node->name); if (len > 63) len = 63; for (int j = 0; j < len; j++) wn[j] = (wchar_t)node->name[j]; wn[len] = 0; draw_text(ctx, wn, 16, row_y, 1.0f, 1.0f, 1.0f, 1.0f); } } static void draw_inspector(ID3D11DeviceContext* ctx, float sw, float sh) { (void)sh; int x = (int)(sw - ED_RIGHT_W + 12); int y = ED_MENU_H + ED_TOOLBAR_H + 28; draw_text(ctx, L"\u5c5e\u6027", x, y - 20, 0.5f, 0.75f, 1.0f, 1.0f); if (!g_selected) { draw_text_a(ctx, "未选择对象", x, y, 0.7f, 0.7f, 0.75f, 1.0f); return; } char buf[128]; int iy = y; snprintf(buf, sizeof(buf), "名称: %s", g_selected->name); draw_text_a(ctx, buf, x, iy, 1.0f, 1.0f, 1.0f, 1.0f); iy += 24; float rx, ry, rz; quat_to_euler(&g_selected->rotation, &rx, &ry, &rz); snprintf(buf, sizeof(buf), "位置: %.2f %.2f %.2f", g_selected->position.x, g_selected->position.y, g_selected->position.z); draw_text_a(ctx, buf, x, iy, 0.85f, 0.85f, 0.88f, 1.0f); iy += 22; snprintf(buf, sizeof(buf), "旋转: %.1f %.1f %.1f", rx * 57.2958f, ry * 57.2958f, rz * 57.2958f); draw_text_a(ctx, buf, x, iy, 0.85f, 0.85f, 0.88f, 1.0f); iy += 22; snprintf(buf, sizeof(buf), "缩放: %.2f %.2f %.2f", g_selected->scale.x, g_selected->scale.y, g_selected->scale.z); draw_text_a(ctx, buf, x, iy, 0.85f, 0.85f, 0.88f, 1.0f); iy += 30; draw_text_a(ctx, "WASD 平移 | QE 升降", x, iy, 0.6f, 0.7f, 0.85f, 1.0f); iy += 20; draw_text_a(ctx, "方向键 旋转 | +/- 缩放", x, iy, 0.6f, 0.7f, 0.85f, 1.0f); } static void draw_console(ID3D11DeviceContext* ctx, float sh) { int y = (int)(sh - ED_BOTTOM_H + 24); draw_text(ctx, L"\u63a7\u5236\u53f0", 12, y - 18, 0.5f, 0.75f, 1.0f, 1.0f); int cy = y; for (int i = g_console_count - 1; i >= 0 && i >= g_console_count - 6; i--) { wchar_t wl[256]; int len = (int)strlen(g_console[i]); if (len > 255) len = 255; for (int j = 0; j < len; j++) wl[j] = (wchar_t)g_console[i][j]; wl[len] = 0; draw_text(ctx, wl, 12, cy, 0.75f, 0.75f, 0.80f, 1.0f); cy += 20; } } static void draw_ui(ID3D11DeviceContext* ctx, float sw, float sh) { ui_begin(ctx, sw, sh); draw_quad(ctx, 0, 0, sw, sh, 0.5f, 0, 0, 0.3f); /* debug overlay */ draw_panel_bg(ctx, sw, sh); /* title / menus */ draw_text(ctx, L"Lity3D Engine Editor", 12, 7, 0.6f, 0.8f, 1.0f, 1.0f); const wchar_t* menus[] = {L"\u6587\u4ef6", L"\u7f16\u8f91", L"\u89c6\u56fe", L"\u6784\u5efa", L"\u5e2e\u52a9"}; int mx = 160; for (int i = 0; i < 5; i++) { draw_text(ctx, menus[i], mx, 8, 0.9f, 0.9f, 0.92f, 1.0f); mx += 70; } draw_hierarchy(ctx, sh); draw_inspector(ctx, sw, sh); draw_console(ctx, sh); draw_text_a(ctx, g_status, 12, (int)(sh - 18), 0.65f, 0.65f, 0.70f, 1.0f); } /* -------------------------------------------------------------------------- */ /* Input / interaction */ /* -------------------------------------------------------------------------- */ static void update_status(void) { if (g_selected) { snprintf(g_status, sizeof(g_status), "%s | pos(%.2f,%.2f,%.2f) | FOV %.0f | Esc quit | Ctrl+S save", g_selected->name, g_selected->position.x, g_selected->position.y, g_selected->position.z, g_camera.fov_y * 57.2958f); } else { snprintf(g_status, sizeof(g_status), "无选中对象 | FOV %.0f | Esc quit | Ctrl+S save", g_camera.fov_y * 57.2958f); } } static void process_hierarchy_click(int mx, int my) { float body_top = ED_MENU_H + ED_TOOLBAR_H + 1; float body_h = (float)g_gdev->height - ED_MENU_H - ED_TOOLBAR_H - ED_BOTTOM_H; if (mx < 0 || mx > ED_LEFT_W || my < body_top || my > body_top + body_h) return; int list_y = ED_MENU_H + ED_TOOLBAR_H + 28; int idx = (my - list_y + 2) / 22 + 1; if (idx >= 1 && idx < g_scene->node_count) { SceneNode* node = &g_scene->nodes[idx]; if (node->id >= 0) { g_selected = node; char buf[128]; snprintf(buf, sizeof(buf), "选中: %s", node->name); console_add(buf); update_status(); } } } static void on_mouse_move(Engine* engine, int x, int y) { (void)engine; g_mouse_x = x; g_mouse_y = y; if (g_camera_drag && g_mouse_down[1]) { float dx = (float)(x - g_mouse_last_x); float dy = (float)(y - g_mouse_last_y); camera_rotate_yaw(&g_camera, dx * 0.005f); camera_rotate_pitch(&g_camera, dy * 0.005f); g_mouse_last_x = x; g_mouse_last_y = y; } else if (g_mouse_down[1]) { g_camera_drag = 1; g_mouse_last_x = x; g_mouse_last_y = y; } else { g_camera_drag = 0; } } static void on_mouse_button(Engine* engine, int button, int pressed) { (void)engine; if (button >= 0 && button < 3) g_mouse_down[button] = pressed; if (button == 0 && pressed) { process_hierarchy_click(g_mouse_x, g_mouse_y); } } static void on_key(Engine* engine, int key) { if (key == VK_ESCAPE) { engine_request_quit(engine); return; } if (GetAsyncKeyState(VK_CONTROL) & 0x8000) { if (key == 'S' || key == 's') { if (scene_save(g_scene, "scene.txt")) { console_add("已保存 scene.txt"); } else { console_add("保存 scene.txt 失败"); } return; } if (key == 'O' || key == 'o') { Scene* new_scene = scene_load("scene.txt", g_gdev->device, (Shader*)asset_data(g_shader_asset)); if (new_scene) { scene_destroy_renderers(g_scene); scene_destroy(g_scene); free(g_scene); g_scene = new_scene; g_selected = NULL; console_add("已加载 scene.txt"); } else { console_add("加载 scene.txt 失败"); } return; } } if (!g_selected) return; float speed = 0.1f; if (GetAsyncKeyState(VK_SHIFT) & 0x8000) speed = 0.5f; Vec3 pos = g_selected->position; switch (key) { case 'W': case 'w': pos.z += speed; break; case 'S': case 's': pos.z -= speed; break; case 'A': case 'a': pos.x -= speed; break; case 'D': case 'd': pos.x += speed; break; case 'Q': case 'q': pos.y += speed; break; case 'E': case 'e': pos.y -= speed; break; default: break; } if (pos.x != g_selected->position.x || pos.y != g_selected->position.y || pos.z != g_selected->position.z) { scene_set_position(g_selected, pos.x, pos.y, pos.z); } float rx, ry, rz; quat_to_euler(&g_selected->rotation, &rx, &ry, &rz); int rot_changed = 0; switch (key) { case VK_LEFT: ry -= 0.05f; rot_changed = 1; break; case VK_RIGHT: ry += 0.05f; rot_changed = 1; break; case VK_UP: rx -= 0.05f; rot_changed = 1; break; case VK_DOWN: rx += 0.05f; rot_changed = 1; break; case VK_PRIOR: rz += 0.05f; rot_changed = 1; break; /* PageUp */ case VK_NEXT: rz -= 0.05f; rot_changed = 1; break; /* PageDown */ default: break; } if (rot_changed) scene_set_rotation_euler(g_selected, rx, ry, rz); Vec3 scale = g_selected->scale; int scale_changed = 0; switch (key) { case VK_ADD: scale.x += 0.1f; scale.y += 0.1f; scale.z += 0.1f; scale_changed = 1; break; case VK_SUBTRACT: scale.x -= 0.1f; scale.y -= 0.1f; scale.z -= 0.1f; scale_changed = 1; break; default: break; } if (scale_changed) scene_set_scale(g_selected, scale.x, scale.y, scale.z); update_status(); } static void on_resize(Engine* engine, int width, int height) { (void)engine; if (width > 0 && height > 0) { renderer_resize(g_renderer, width, height); g_camera.aspect = (float)width / (float)height; g_camera.dirty = 1; } } /* -------------------------------------------------------------------------- */ /* Render callback */ /* -------------------------------------------------------------------------- */ static void on_render(Engine* engine) { (void)engine; if (!g_renderer || !g_gdev) return; renderer_set_view(g_renderer, camera_get_view(&g_camera)); renderer_set_projection(g_renderer, camera_get_projection(&g_camera)); renderer_begin_frame(g_renderer); if (g_scene) { scene_update(g_scene); scene_render(g_scene, g_renderer); } draw_ui(g_gdev->context, (float)g_gdev->width, (float)g_gdev->height); renderer_end_frame(g_renderer); if (engine->frame_count == 60) { graphics_device_save_screenshot(g_gdev, "editor_screenshot.bmp"); console_add("已保存 editor_screenshot.bmp"); } } static void on_update(Engine* engine, double dt) { (void)engine; (void)dt; } /* -------------------------------------------------------------------------- */ /* Main */ /* -------------------------------------------------------------------------- */ int WINAPI WinMain(HINSTANCE hI, HINSTANCE hP, LPSTR cmd, int nS) { (void)hI; (void)hP; (void)nS; logger_init("engine.log"); LOG_INFO("=== Lity3D Engine Editor ==="); EngineConfig cfg = engine_default_config(); cfg.app_name = "Lity3D Engine Editor"; cfg.window_width = 1440; cfg.window_height = 900; cfg.vsync = 1; g_engine = engine_create(); if (!engine_init(g_engine, &cfg)) { LOG_FATAL("Engine init failed"); return -1; } g_gdev = engine_get_graphics_device(g_engine); g_renderer = engine_get_renderer(g_engine); g_assets = asset_manager_create(); g_shader_asset = asset_manager_load_shader(g_assets, g_gdev->device, NULL, NULL); if (!g_shader_asset) { LOG_FATAL("Shader load failed"); return -1; } Shader* default_shader = (Shader*)asset_data(g_shader_asset); g_scene = scene_load("scene.txt", g_gdev->device, default_shader); if (!g_scene) { LOG_INFO("Creating default editor scene"); g_scene = create_default_editor_scene(g_assets, g_gdev->device, default_shader); scene_save(g_scene, "scene.txt"); } float aspect = (float)cfg.window_width / cfg.window_height; g_camera = camera_perspective(1.0472f, aspect, 0.1f, 100.0f); g_camera.position = (Vec3){0, 1.5f, -5.0f}; g_camera.target = (Vec3){0, 0, 0}; g_camera.dirty = 1; g_atlas_ok = glyph_atlas_init(&g_atlas, g_gdev->device); if (!g_atlas_ok) LOG_WARN("GlyphAtlas init failed"); create_ui_states(g_gdev->device); if (!compile_ui_shaders(g_gdev->device)) { LOG_FATAL("UI shader compile failed"); return -1; } console_add("Lity3D Engine Editor ready"); console_add("Ctrl+S 保存 | Ctrl+O 加载 | Esc 退出"); console_add("左键点选层级,右键拖拽旋转视角"); update_status(); engine_set_update_callback(g_engine, on_update); engine_set_render_callback(g_engine, on_render); engine_set_key_callback(g_engine, on_key); engine_set_resize_callback(g_engine, on_resize); engine_set_mouse_move_callback(g_engine, on_mouse_move); engine_set_mouse_button_callback(g_engine, on_mouse_button); engine_run(g_engine); if (g_scene) { scene_destroy_renderers(g_scene); scene_destroy(g_scene); free(g_scene); } asset_manager_destroy(g_assets); if (g_ui_sampler) g_ui_sampler->lpVtbl->Release(g_ui_sampler); if (g_ui_blend) g_ui_blend->lpVtbl->Release(g_ui_blend); if (g_ui_raster) g_ui_raster->lpVtbl->Release(g_ui_raster); if (g_ui_depth) g_ui_depth->lpVtbl->Release(g_ui_depth); if (g_ui_cb) g_ui_cb->lpVtbl->Release(g_ui_cb); if (g_ui_ps) g_ui_ps->lpVtbl->Release(g_ui_ps); if (g_ui_vs) g_ui_vs->lpVtbl->Release(g_ui_vs); if (g_ui_layout) g_ui_layout->lpVtbl->Release(g_ui_layout); if (g_atlas_ok) glyph_atlas_destroy(&g_atlas); engine_shutdown(g_engine); logger_shutdown(); return 0; }