njin
Engine 2D gọn nhẹ để tự tay làm game theo ý mình. Vibe coding cũng ổn luôn
Loading...
Searching...
No Matches
Đường cong (spline)

Trang này cho vật đi dọc một đường cong mượt: camera chạy trên ray trong cutscene, bục di chuyển, quái tuần tra, đường bay của rồng, quỹ đạo đạn cong, đường đua. Mọi thứ khai báo trong njin_spline.h, chạy cho cả 2D (njin::spline2d) lẫn 3D (njin::spline3d) với cùng tên hàm.

Cần biết trước: Toán học, va chạm, số ngẫu nhiên (vec2, vec3).

Hai loại đường cong

Loại Đặt điểm thế nào Hợp với
spline_catmull_rom (mặc định) Đường đi qua mọi điểm Đường ray, lối tuần tra: chấm điểm là xong
spline_bezier Điểm 0, 3, 6... nằm trên đường; hai điểm giữa mỗi cặp là tay cầm kéo đường Đường cần chỉnh dáng kỹ, như công cụ pen của trình vẽ

Catmull-Rom mặc định dùng dạng centripetal (alpha = 0.5): khi các điểm cách nhau không đều, đường không thắt nút hay vòng xoắn. closed = true nối điểm cuối về điểm đầu thành vòng kín.

rail.points = {{12, 4, 0}, {6, 6, 10}, {-8, 5, 9}, {-12, 3, -2}};
rail.closed = true;
njin::spline_bake(rail); // sau mỗi lần sửa điểm
void spline_bake(spline3d &spline, i32 steps=64)
Lập bảng độ dài sau khi sửa điểm.
Một đường cong 3D: đường ray của camera, bục di chuyển, lối bay, đường tuần tra.
Definition njin_spline.h:29
bool closed
Đường kín: điểm cuối nối về điểm đầu.
Definition njin_spline.h:32
std::vector< vec3 > points
Các điểm điều khiển.
Definition njin_spline.h:30

Đi theo tham số hay theo khoảng cách

spline_point() đọc đường theo tham số t: phần nguyên là đoạn, phần lẻ là vị trí trong đoạn; với Catmull-Rom, t = 2 là đúng điểm thứ 2. Nhưng đi đều theo t thì không đều theo khoảng cách: đoạn dài đi nhanh, đoạn ngắn đi chậm.

Muốn đi đều (camera không giật, bục không lúc nhanh lúc chậm) thì đi theo khoảng cách: spline_bake() lập một bảng độ dài, rồi spline_point_at() và spline_tangent_at() nhận khoảng cách tính từ đầu đường. spline_length() là độ dài cả đường.

Hàm Trả lời
spline_point(), spline_tangent() Điểm và hướng ở tham số t
spline_point_at(), spline_tangent_at() Điểm và hướng ở một khoảng cách dọc đường
spline_t_at() Tham số t ở một khoảng cách
spline_length() Độ dài cả đường
spline_nearest() Chỗ gần một điểm nhất trên đường, và khoảng cách dọc đường tới đó: bám vào ray, biết người chơi đã chạy được bao xa trên đường đua
spline_draw_debug() Vẽ đường và điểm điều khiển bằng gizmo

Đi dọc đường mỗi frame

njin::spline_follower giữ trạng thái của một vật đang đi: đã đi bao xa, tốc độ, và đến cuối thì làm gì (spline_stop, spline_loop, spline_ping_pong). Mỗi frame gọi spline_follow():

njin::spline_follower mover{.speed = 4.0f, .end = njin::spline_loop};
// mỗi frame:
const njin::vec3 pos = njin::spline_follow(rail, mover, njin::delta(ctx));
const njin::vec3 dir = njin::spline_tangent_at(rail, mover.distance);
vec3 spline_tangent_at(const spline3d &spline, f32 distance)
Hướng ở khoảng cách distance, độ dài 1.
vec3 spline_follow(const spline3d &spline, spline_follower &follower, f32 dt)
Đi tiếp dt giây dọc đường và trả về chỗ mới.
@ spline_loop
Quay về đầu (đường kín thì đi tiếp vòng mới).
Definition njin_spline.h:135
f32 delta(const context &ctx)
Thời gian của frame trước, tính bằng giây, đã nhân với tốc độ thời gian.
Trạng thái của một vật đi dọc đường với tốc độ đều: camera trên ray, bục di chuyển,...
Definition njin_spline.h:141
f32 distance
Đã đi được bao xa từ đầu đường.
Definition njin_spline.h:142
Vector 3 chiều.
Definition _types.h:47

Với spline_stop, mover.finished thành true khi tới cuối. Với spline_ping_pong, speed đổi dấu mỗi lần quay đầu. Đi ngược thì đặt speed âm.

Ví dụ đầy đủ

Camera chạy vòng trên một ray kín và nhìn theo hướng ray; một bục đi tới đi lui dọc một đường Bezier; phím Tab ẩn hiện các đường.

#include <njin.h>
namespace {
using namespace njin;
spline3d rail; // đường ray kín của camera, đi qua từng điểm
spline3d lift; // đường bay của bục, Bezier có tay cầm
spline_follower cam_mover{.speed = 4.0f, .end = spline_loop};
spline_follower lift_mover{.speed = 2.0f, .end = spline_ping_pong};
bool show_rails = true;
void load(context &) {
rail.points = {{12, 4, 0}, {6, 6, 10}, {-8, 5, 9}, {-12, 3, -2}, {-4, 7, -12}, {8, 4, -9}};
rail.closed = true;
spline_bake(rail); // sau khi sửa điểm: lập bảng để đi đều theo khoảng cách
lift.points = {{-4, 1, 0}, {-4, 6, 0}, {4, 6, 0}, {4, 1, 0}}; // đầu, hai tay cầm, cuối
spline_bake(lift);
}
void update(context &ctx) {
if (key_pressed(ctx, key_tab))
show_rails = !show_rails;
}
void render(context &ctx) {
// Camera chạy đều trên ray và nhìn trước theo hướng ray.
const vec3 eye = spline_follow(rail, cam_mover, delta(ctx));
const vec3 ahead = spline_tangent_at(rail, cam_mover.distance);
const camera3d camera{.position = eye, .target = eye + ahead * 4.0f + vec3{0.0f, -1.5f, 0.0f}, .fovy = 60.0f};
// Bục đi tới đi lui dọc đường cong.
const vec3 platform = spline_follow(lift, lift_mover, delta(ctx));
light3d_set(ctx, {.direction = {-0.4f, -1.0f, -0.3f}, .shadows = true, .shadow_range = 40.0f});
begin_3d(ctx, camera);
draw_plane3d(ctx, {0.0f, 0.0f, 0.0f}, {40.0f, 40.0f}, rgba{0.55f, 0.6f, 0.5f, 1.0f});
draw_cube3d(ctx, platform, {2.0f, 0.3f, 2.0f}, rgba{0.8f, 0.5f, 0.2f, 1.0f});
if (show_rails) {
spline_draw_debug(ctx, rail);
}
end_3d(ctx);
}
void setup(context &ctx) {
ecs_register(ctx, phase_startup, load, "load");
ecs_register(ctx, phase_update, update, "update");
ecs_register(ctx, phase_render, render, "render");
}
} // namespace
mod_desc rails_module() { return {.name = "rails", .setup = setup}; }
void light3d_set(context &ctx, const light3d &light)
Đặt ánh sáng chung.
void begin_3d(context &ctx, const camera3d &camera)
Bắt đầu vẽ 3D qua camera.
void draw_plane3d(const context &ctx, vec3 center, vec2 size, rgba color)
Vẽ mặt phẳng nằm ngang (song song mặt xz), mặt trên hướng +y.
void draw_cube3d(const context &ctx, vec3 center, vec3 size, rgba color)
Vẽ hộp đặc, các cạnh song song với trục.
void end_3d(context &ctx)
Kết thúc vẽ 3D và quay về vẽ 2D trong không gian thế giới.
bool key_pressed(const context &ctx, key_code key)
Đúng ở frame mà phím vừa được nhấn xuống (chỉ một frame).
@ key_tab
Tab.
Definition _types.h:413
void ecs_register(context &ctx, sys_phase phase, sys_fnc fnc, const char *name=nullptr)
Thêm một system vào lịch chạy của một phase.
@ phase_update
Mỗi frame, logic chính của game.
Definition _mod.h:35
@ phase_startup
Một lần, trước frame đầu tiên.
Definition _mod.h:32
@ phase_render
Mỗi frame, vẽ trong không gian thế giới.
Definition _mod.h:38
void spline_draw_debug(context &ctx, const spline3d &spline, rgba color={1.0f, 0.8f, 0.2f, 1.0f})
Vẽ đường và các điểm điều khiển bằng gizmo (njin_gizmo.h) trong frame này.
@ spline_bezier
Bezier bậc ba: điểm 0, 3, 6... nằm trên đường, hai điểm giữa mỗi cặp là tay cầm kéo đường cong (như c...
Definition njin_spline.h:21
@ spline_ping_pong
Quay đầu đi ngược lại, rồi lại quay đầu ở đầu đường.
Definition njin_spline.h:136
constexpr rgba green
Xanh lá.
Definition _math.h:237
Camera phối cảnh cho begin_3d().
Definition njin_3d.h:15
Mô tả một module: một nhóm system có tên.
Definition _mod.h:67
Màu gồm đỏ, xanh lá, xanh dương và alpha.
Definition _types.h:64
spline_kind kind
Loại đường cong.
Definition njin_spline.h:31

Giới hạn

  • Hướng (tangent) chỉ là một vector: không có góc lăn (roll) dọc đường. Camera trên ray tự giữ up của nó.
  • Bảng độ dài cần lập lại (spline_bake()) sau mỗi lần sửa điểm; thiếu bảng thì các hàm theo khoảng cách tự lập một bảng tạm mỗi lần gọi, đúng nhưng chậm.