Files
lity3d/Demo/editor_main.c
T
shuiyu1123 7a0533d21a sync: user's Lity3D_source.zip changes
- Engine core: improved initialization and lifecycle
- Renderer: camera and rendering pipeline updates
- Editor: updated editor code
- Window: improved window handling
- Demo/main.c: scene demo with AssetManager + scene loading
- build.bat: updated build script
- 11 files modified from user's manual edits
2026-07-12 12:13:50 +08:00

683 lines
28 KiB
C

#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 <d3dcompiler.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
/* -------------------------------------------------------------------------- */
/* 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;
}