385 lines
13 KiB
C
385 lines
13 KiB
C
#include "../Engine/Core/engine.h"
|
|
#include "../Engine/Core/logger.h"
|
|
#include "../Engine/Core/timer.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/mesh.h"
|
|
#include "../Engine/Rendering/material.h"
|
|
#include "../Engine/Rendering/camera.h"
|
|
#include "../Engine/Rendering/camera_controller.h"
|
|
#include "../Engine/Terrain/terrain.h"
|
|
#include "../Engine/Terrain/terrain_manager.h"
|
|
#include "../Engine/Rendering/frustum.h"
|
|
#include "../Engine/Scene/scene_graph.h"
|
|
#include "../Engine/Scene/scene_render.h"
|
|
#include "../Engine/Scene/scene_serialize.h"
|
|
#include "../Engine/include/engine_api.h"
|
|
#include "../Engine/ASM/math_c_fallback.h"
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <Windows.h>
|
|
|
|
static AssetManager* g_assets = NULL;
|
|
static Asset* g_shader_asset = NULL;
|
|
static Camera g_camera;
|
|
static CameraController g_controller;
|
|
static TerrainManager* g_terrain = NULL;
|
|
static Material* g_terrain_mat = NULL;
|
|
static Mesh* g_cube = NULL;
|
|
static Material* g_cube_mat = NULL;
|
|
static Mesh* g_water = NULL;
|
|
static Material* g_water_mat = NULL;
|
|
static Scene* g_test_scene = NULL;
|
|
|
|
static int g_mouse_down[3] = {0};
|
|
static int g_last_mouse_x = 0, g_last_mouse_y = 0;
|
|
static int g_mouse_first = 1;
|
|
static int g_walk_mode = 0;
|
|
|
|
// ============================================================================
|
|
// 简易植被/岩石散布
|
|
// ============================================================================
|
|
#define MAX_SCATTER 512
|
|
|
|
typedef struct {
|
|
Mesh* mesh;
|
|
Material* mat;
|
|
Mat4 world;
|
|
} ScatterInstance;
|
|
|
|
static ScatterInstance g_scatter[MAX_SCATTER];
|
|
static int g_scatter_count = 0;
|
|
|
|
static void add_scatter(Mesh* mesh, Material* mat, Vec3 pos, Vec3 scale) {
|
|
if (g_scatter_count >= MAX_SCATTER) return;
|
|
ScatterInstance* s = &g_scatter[g_scatter_count++];
|
|
s->mesh = mesh;
|
|
s->mat = mat;
|
|
mat4_identity(&s->world);
|
|
s->world.m[0][0] = scale.x;
|
|
s->world.m[1][1] = scale.y;
|
|
s->world.m[2][2] = scale.z;
|
|
s->world.m[3][0] = pos.x;
|
|
s->world.m[3][1] = pos.y;
|
|
s->world.m[3][2] = pos.z;
|
|
}
|
|
|
|
static void generate_scatter(ID3D11Device* dev, Shader* shader) {
|
|
Mesh* rock_mesh = mesh_create_cube(dev);
|
|
Material* rock_mat = material_create(shader, NULL);
|
|
rock_mat->color = (Vec4){0.55f, 0.50f, 0.45f, 1.0f};
|
|
|
|
Mesh* trunk_mesh = mesh_create_cube(dev);
|
|
Material* trunk_mat = material_create(shader, NULL);
|
|
trunk_mat->color = (Vec4){0.55f, 0.40f, 0.25f, 1.0f};
|
|
|
|
Mesh* leaves_mesh = mesh_create_sphere(dev, 1.0f, 8);
|
|
Material* leaves_mat = material_create(shader, NULL);
|
|
leaves_mat->color = (Vec4){0.22f, 0.65f, 0.28f, 1.0f};
|
|
|
|
for (int i = 0; i < MAX_SCATTER; i++) {
|
|
float x = ((float)rand() / (float)RAND_MAX) * 400.0f - 200.0f;
|
|
float z = ((float)rand() / (float)RAND_MAX) * 400.0f - 200.0f;
|
|
float h = terrain_manager_get_height(g_terrain, x, z);
|
|
// 跳过过低(水)或过高的山顶
|
|
if (h < -2.0f || h > 20.0f) continue;
|
|
|
|
int type = rand() % 3;
|
|
if (type == 0) {
|
|
// 岩石
|
|
float s = 1.0f + ((float)rand() / (float)RAND_MAX) * 2.0f;
|
|
Vec3 pos = {x, h + s * 0.3f, z};
|
|
Vec3 sc = {s, s * 0.6f, s};
|
|
add_scatter(rock_mesh, rock_mat, pos, sc);
|
|
} else {
|
|
// 树
|
|
float trunk_h = 1.5f + ((float)rand() / (float)RAND_MAX) * 1.5f;
|
|
Vec3 trunk_pos = {x, h + trunk_h * 0.5f, z};
|
|
Vec3 trunk_sc = {0.25f, trunk_h, 0.25f};
|
|
add_scatter(trunk_mesh, trunk_mat, trunk_pos, trunk_sc);
|
|
|
|
float leaves_s = 1.2f + ((float)rand() / (float)RAND_MAX) * 1.2f;
|
|
Vec3 leaves_pos = {x, h + trunk_h + leaves_s * 0.4f, z};
|
|
Vec3 leaves_sc = {leaves_s, leaves_s, leaves_s};
|
|
add_scatter(leaves_mesh, leaves_mat, leaves_pos, leaves_sc);
|
|
}
|
|
}
|
|
|
|
LOG_INFO("Generated %d scatter instances", g_scatter_count);
|
|
}
|
|
|
|
static void render_scatter(Renderer* r, const Vec4 planes[6]) {
|
|
const float cull_distance = 140.0f;
|
|
const float cull_dist2 = cull_distance * cull_distance;
|
|
int rendered = 0;
|
|
for (int i = 0; i < g_scatter_count; i++) {
|
|
ScatterInstance* s = &g_scatter[i];
|
|
Vec3 pos = {s->world.m[3][0], s->world.m[3][1], s->world.m[3][2]};
|
|
Vec3 d;
|
|
vec3_sub(&pos, &g_camera.position, &d);
|
|
if (vec3_dot(&d, &d) > cull_dist2) continue;
|
|
|
|
// 视锥剔除:用包围球近似
|
|
float radius = fmaxf(fmaxf(s->world.m[0][0], s->world.m[1][1]), s->world.m[2][2]) * 0.866f;
|
|
if (!frustum_intersects_sphere(planes, pos, radius)) continue;
|
|
|
|
renderer_draw_mesh_with_material(r, s->mesh, s->mat, &s->world);
|
|
rendered++;
|
|
}
|
|
LOG_INFO("Scatter render: visible=%d/%d", rendered, g_scatter_count);
|
|
}
|
|
|
|
// ============================================================================
|
|
// 地形高度回调
|
|
// ============================================================================
|
|
static float get_terrain_height(float x, float z, void* user) {
|
|
(void)user;
|
|
if (g_terrain) return terrain_manager_get_height(g_terrain, x, z);
|
|
return 0.0f;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 引擎回调
|
|
// ============================================================================
|
|
static void on_render(Engine* engine) {
|
|
Renderer* r = engine_get_renderer(engine);
|
|
GraphicsDevice* dev = engine_get_graphics_device(engine);
|
|
if (!r || !dev) return;
|
|
|
|
renderer_set_view(r, camera_get_view(&g_camera));
|
|
renderer_set_projection(r, camera_get_projection(&g_camera));
|
|
|
|
renderer_begin_frame(r);
|
|
|
|
Mat4 vp;
|
|
renderer_get_view_projection(r, &vp);
|
|
Vec4 planes[6];
|
|
frustum_from_matrix(&vp, planes);
|
|
|
|
if (g_water) {
|
|
Mat4 water_world;
|
|
mat4_identity(&water_world);
|
|
water_world.m[3][1] = -5.0f;
|
|
renderer_draw_mesh_with_material(r, g_water, g_water_mat, &water_world);
|
|
}
|
|
|
|
if (g_terrain) {
|
|
terrain_manager_update(g_terrain, g_camera.position);
|
|
terrain_manager_render(g_terrain, r);
|
|
}
|
|
|
|
render_scatter(r, planes);
|
|
|
|
if (g_test_scene) {
|
|
scene_update(g_test_scene);
|
|
scene_render(g_test_scene, r);
|
|
}
|
|
|
|
renderer_end_frame(r);
|
|
|
|
if (engine->frame_count == 60) {
|
|
graphics_device_save_screenshot(dev, "open_world_screenshot1.bmp");
|
|
}
|
|
if (engine->frame_count == 180) {
|
|
graphics_device_save_screenshot(dev, "open_world_screenshot2.bmp");
|
|
}
|
|
}
|
|
|
|
static void on_update(Engine* engine, double dt) {
|
|
(void)engine;
|
|
float fdt = (float)dt;
|
|
|
|
Vec3 dir = {0.0f, 0.0f, 0.0f};
|
|
if (GetAsyncKeyState('W') & 0x8000) dir.z += 1.0f;
|
|
if (GetAsyncKeyState('S') & 0x8000) dir.z -= 1.0f;
|
|
if (GetAsyncKeyState('A') & 0x8000) dir.x -= 1.0f;
|
|
if (GetAsyncKeyState('D') & 0x8000) dir.x += 1.0f;
|
|
|
|
if (!g_walk_mode) {
|
|
if (GetAsyncKeyState(VK_SPACE) & 0x8000) dir.y += 1.0f;
|
|
if (GetAsyncKeyState(VK_SHIFT) & 0x8000) dir.y -= 1.0f;
|
|
}
|
|
|
|
if (dir.x != 0.0f || dir.y != 0.0f || dir.z != 0.0f) {
|
|
camera_controller_move(&g_controller, dir, fdt);
|
|
}
|
|
|
|
camera_controller_update(&g_controller, fdt);
|
|
}
|
|
|
|
static void on_key(Engine* engine, int key) {
|
|
(void)engine;
|
|
if (key == VK_ESCAPE) engine_request_quit(engine);
|
|
|
|
// 切换步行/飞行模式
|
|
if (key == 'F' || key == 'f') {
|
|
g_walk_mode = !g_walk_mode;
|
|
camera_controller_set_mode(&g_controller, g_walk_mode ? CAMERA_MODE_WALK : CAMERA_MODE_FLY);
|
|
g_controller.move_speed = g_walk_mode ? 8.0f : 40.0f;
|
|
LOG_INFO("Camera mode: %s (speed %.1f)", g_walk_mode ? "walk" : "fly", g_controller.move_speed);
|
|
}
|
|
|
|
// 模式切换后重新定位相机高度
|
|
if (g_walk_mode) {
|
|
float ground = get_terrain_height(g_camera.position.x, g_camera.position.z, NULL);
|
|
g_camera.position.y = ground + g_controller.height_offset;
|
|
g_camera.target.y = g_camera.position.y + sinf(g_controller.pitch);
|
|
g_camera.dirty = 1;
|
|
}
|
|
}
|
|
|
|
static void on_mouse_move(Engine* engine, int x, int y) {
|
|
(void)engine;
|
|
if (g_mouse_down[0]) { // 左键拖拽旋转
|
|
if (g_mouse_first) {
|
|
g_last_mouse_x = x;
|
|
g_last_mouse_y = y;
|
|
g_mouse_first = 0;
|
|
} else {
|
|
float dx = (float)(x - g_last_mouse_x);
|
|
float dy = (float)(y - g_last_mouse_y);
|
|
camera_controller_look(&g_controller, dx, dy);
|
|
g_last_mouse_x = x;
|
|
g_last_mouse_y = y;
|
|
}
|
|
} else {
|
|
g_mouse_first = 1;
|
|
}
|
|
}
|
|
|
|
static void on_mouse_button(Engine* engine, int button, int pressed) {
|
|
(void)engine;
|
|
if (button >= 0 && button < 3) g_mouse_down[button] = pressed;
|
|
}
|
|
|
|
static void on_resize(Engine* engine, int width, int height) {
|
|
Renderer* r = engine_get_renderer(engine);
|
|
if (r && width > 0 && height > 0) {
|
|
renderer_resize(r, width, height);
|
|
g_camera.aspect = (float)width / (float)height;
|
|
g_camera.dirty = 1;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// 入口
|
|
// ============================================================================
|
|
int main(int argc, char* argv[]) {
|
|
(void)argc; (void)argv;
|
|
|
|
srand((unsigned int)time(NULL));
|
|
|
|
EngineConfig config = engine_default_config();
|
|
config.app_name = "Lity3D - Open World Demo";
|
|
config.vsync = 1;
|
|
config.window_width = 1280;
|
|
config.window_height = 720;
|
|
|
|
Engine* engine = engine_create();
|
|
if (!engine_init(engine, &config)) {
|
|
LOG_FATAL("Engine init failed");
|
|
return -1;
|
|
}
|
|
|
|
GraphicsDevice* dev = engine_get_graphics_device(engine);
|
|
Renderer* r = engine_get_renderer(engine);
|
|
|
|
g_assets = asset_manager_create();
|
|
g_shader_asset = asset_manager_load_shader(g_assets, dev->device, NULL, NULL);
|
|
if (!g_shader_asset) {
|
|
LOG_FATAL("Shader load failed");
|
|
return -1;
|
|
}
|
|
LOG_INFO("Shader loaded via AssetManager");
|
|
|
|
Shader* default_shader = (Shader*)asset_data(g_shader_asset);
|
|
|
|
// 地形材质
|
|
g_terrain_mat = material_create(default_shader, NULL);
|
|
g_terrain_mat->color = (Vec4){1.0f, 1.0f, 1.0f, 1.0f};
|
|
|
|
// 参照立方体
|
|
g_cube = mesh_create_cube(dev->device);
|
|
g_cube_mat = material_create(default_shader, NULL);
|
|
g_cube_mat->color = (Vec4){1.0f, 0.2f, 0.2f, 1.0f};
|
|
|
|
// 水面(不透明,作为低洼处背景)
|
|
g_water = mesh_create_plane(dev->device, 2000.0f);
|
|
g_water_mat = material_create(default_shader, NULL);
|
|
g_water_mat->color = (Vec4){0.18f, 0.45f, 0.78f, 1.0f};
|
|
|
|
// 测试场景:仅保留一个红色立方体作为原点标记
|
|
g_test_scene = (Scene*)calloc(1, sizeof(Scene));
|
|
scene_init(g_test_scene);
|
|
|
|
SceneNode* cube_node = scene_create_node(g_test_scene, "cube");
|
|
scene_set_parent(cube_node, g_test_scene->root);
|
|
cube_node->position = (Vec3){0.0f, 0.0f, 0.0f};
|
|
MeshRenderer* cube_mr = mesh_renderer_create(MESH_CUBE, g_cube, g_cube_mat);
|
|
scene_node_set_mesh_renderer(cube_node, cube_mr);
|
|
|
|
// 地形配置
|
|
TerrainConfig tcfg = terrain_default_config();
|
|
tcfg.vertices_per_chunk = 48;
|
|
tcfg.chunk_size = 64.0f;
|
|
tcfg.height_scale = 30.0f;
|
|
tcfg.uv_scale = 0.05f;
|
|
|
|
g_terrain = terrain_manager_create(dev->device, &tcfg, g_terrain_mat);
|
|
g_terrain->load_radius = 3;
|
|
|
|
// 生成植被/岩石
|
|
generate_scatter(dev->device, default_shader);
|
|
|
|
// 天空色与柔和环境光
|
|
renderer_set_clear_color(r, 0.53f, 0.81f, 0.98f, 1.0f);
|
|
renderer_set_light(r, (Vec3){0.5f, -1.0f, 0.3f},
|
|
(Vec4){0.55f, 0.53f, 0.48f, 1.0f},
|
|
(Vec4){0.70f, 0.74f, 0.82f, 1.0f});
|
|
|
|
// 相机初始化:站在地形上,朝 +Z
|
|
float aspect = (float)config.window_width / (float)config.window_height;
|
|
g_camera = camera_perspective(1.0472f, aspect, 0.1f, 500.0f);
|
|
float start_h = terrain_manager_get_height(g_terrain, 0.0f, 0.0f);
|
|
g_camera.position = (Vec3){0.0f, start_h + 1.75f, 0.0f};
|
|
g_camera.target = (Vec3){0.0f, start_h + 1.75f, 5.0f};
|
|
g_camera.far_z = 500.0f;
|
|
g_camera.dirty = 1;
|
|
|
|
g_controller = camera_controller_create(&g_camera);
|
|
g_controller.move_speed = 8.0f;
|
|
g_controller.mouse_sensitivity = 0.004f;
|
|
camera_controller_set_ground_callback(&g_controller, get_terrain_height, NULL);
|
|
g_walk_mode = 1;
|
|
camera_controller_set_mode(&g_controller, CAMERA_MODE_WALK);
|
|
LOG_INFO("Starting in walk mode at height %.2f", start_h);
|
|
|
|
renderer_set_view(r, camera_get_view(&g_camera));
|
|
renderer_set_projection(r, camera_get_projection(&g_camera));
|
|
|
|
engine_set_update_callback(engine, on_update);
|
|
engine_set_render_callback(engine, on_render);
|
|
engine_set_key_callback(engine, on_key);
|
|
engine_set_mouse_move_callback(engine, on_mouse_move);
|
|
engine_set_mouse_button_callback(engine, on_mouse_button);
|
|
engine_set_resize_callback(engine, on_resize);
|
|
|
|
LOG_INFO("Running open world demo...");
|
|
engine_run(engine);
|
|
|
|
terrain_manager_destroy(g_terrain);
|
|
if (g_test_scene) {
|
|
scene_destroy_renderers(g_test_scene);
|
|
scene_destroy(g_test_scene);
|
|
free(g_test_scene);
|
|
}
|
|
if (g_cube) mesh_destroy(g_cube);
|
|
asset_manager_destroy(g_assets);
|
|
engine_shutdown(engine);
|
|
return 0;
|
|
}
|