Feature/physics and tilemap enhancement (#247)

* feat(behavior-tree,tilemap): 修复编辑器连线缩放问题并增强插件系统

* feat(node-editor,blueprint): 新增通用节点编辑器和蓝图可视化脚本系统

* feat(editor,tilemap): 优化编辑器UI样式和Tilemap编辑器功能

* fix: 修复CodeQL安全警告和CI类型检查错误

* fix: 修复CodeQL安全警告和CI类型检查错误

* fix: 修复CodeQL安全警告和CI类型检查错误
This commit is contained in:
YHH
2025-11-29 23:00:48 +08:00
committed by GitHub
parent f03b73b58e
commit 359886c72f
198 changed files with 33879 additions and 13121 deletions

View File

@@ -222,8 +222,8 @@ impl Engine {
// Render gizmos on top
if self.show_gizmos {
self.gizmo_renderer.render(self.context.gl(), self.renderer.camera());
// Render axis indicator in corner (always visible when gizmos are on)
// 在角落渲染坐标轴指示器(当 gizmos 开启时始终可见)
// Render axis indicator in corner
// 在角落渲染坐标轴指示器
self.gizmo_renderer.render_axis_indicator(
self.context.gl(),
self.context.width() as f32,
@@ -267,6 +267,52 @@ impl Engine {
self.gizmo_renderer.add_rect(x, y, width, height, rotation, origin_x, origin_y, r, g, b, a, show_handles);
}
/// Add a circle gizmo.
/// 添加圆形Gizmo。
pub fn add_gizmo_circle(
&mut self,
x: f32,
y: f32,
radius: f32,
r: f32,
g: f32,
b: f32,
a: f32,
) {
self.gizmo_renderer.add_circle(x, y, radius, r, g, b, a);
}
/// Add a line gizmo.
/// 添加线条Gizmo。
pub fn add_gizmo_line(
&mut self,
points: Vec<f32>,
r: f32,
g: f32,
b: f32,
a: f32,
closed: bool,
) {
self.gizmo_renderer.add_line(points, r, g, b, a, closed);
}
/// Add a capsule gizmo.
/// 添加胶囊Gizmo。
pub fn add_gizmo_capsule(
&mut self,
x: f32,
y: f32,
radius: f32,
half_height: f32,
rotation: f32,
r: f32,
g: f32,
b: f32,
a: f32,
) {
self.gizmo_renderer.add_capsule(x, y, radius, half_height, rotation, r, g, b, a);
}
/// Set transform tool mode.
/// 设置变换工具模式。
pub fn set_transform_mode(&mut self, mode: u8) {

View File

@@ -322,6 +322,55 @@ impl GameEngine {
self.engine.add_gizmo_rect(x, y, width, height, rotation, origin_x, origin_y, r, g, b, a, show_handles);
}
/// Add a circle gizmo outline.
/// 添加圆形Gizmo边框。
#[wasm_bindgen(js_name = addGizmoCircle)]
pub fn add_gizmo_circle(
&mut self,
x: f32,
y: f32,
radius: f32,
r: f32,
g: f32,
b: f32,
a: f32,
) {
self.engine.add_gizmo_circle(x, y, radius, r, g, b, a);
}
/// Add a line gizmo.
/// 添加线条Gizmo。
#[wasm_bindgen(js_name = addGizmoLine)]
pub fn add_gizmo_line(
&mut self,
points: Vec<f32>,
r: f32,
g: f32,
b: f32,
a: f32,
closed: bool,
) {
self.engine.add_gizmo_line(points, r, g, b, a, closed);
}
/// Add a capsule gizmo outline.
/// 添加胶囊Gizmo边框。
#[wasm_bindgen(js_name = addGizmoCapsule)]
pub fn add_gizmo_capsule(
&mut self,
x: f32,
y: f32,
radius: f32,
half_height: f32,
rotation: f32,
r: f32,
g: f32,
b: f32,
a: f32,
) {
self.engine.add_gizmo_capsule(x, y, radius, half_height, rotation, r, g, b, a);
}
/// Set transform tool mode.
/// 设置变换工具模式。
///

View File

@@ -58,10 +58,30 @@ pub struct GizmoRenderer {
/// Pending rectangle data: [x, y, width, height, rotation, origin_x, origin_y, r, g, b, a, show_handles]
/// 待渲染的矩形数据
rects: Vec<f32>,
/// Pending circle data: [x, y, radius, r, g, b, a, segments]
/// 待渲染的圆形数据
circles: Vec<f32>,
/// Pending line data: stored as separate line commands
/// 待渲染的线条数据
lines: Vec<LineGizmo>,
/// Pending capsule data: [x, y, radius, half_height, rotation, r, g, b, a]
/// 待渲染的胶囊数据
capsules: Vec<f32>,
/// Current transform mode
transform_mode: TransformMode,
}
/// Line gizmo data
/// 线条 gizmo 数据
struct LineGizmo {
points: Vec<f32>,
r: f32,
g: f32,
b: f32,
a: f32,
closed: bool,
}
impl GizmoRenderer {
/// Create a new gizmo renderer.
/// 创建新的Gizmo渲染器。
@@ -74,6 +94,9 @@ impl GizmoRenderer {
program,
vertex_buffer,
rects: Vec::new(),
circles: Vec::new(),
lines: Vec::new(),
capsules: Vec::new(),
transform_mode: TransformMode::default(),
})
}
@@ -129,6 +152,9 @@ impl GizmoRenderer {
/// 清空所有待渲染的Gizmo。
pub fn clear(&mut self) {
self.rects.clear();
self.circles.clear();
self.lines.clear();
self.capsules.clear();
}
/// Add a rectangle outline gizmo.
@@ -164,8 +190,57 @@ impl GizmoRenderer {
]);
}
/// Render axis indicator at top-right corner of the viewport.
/// 在视口右上角渲染坐标轴指示器
/// Add a circle outline gizmo.
/// 添加圆形边框Gizmo
pub fn add_circle(
&mut self,
x: f32,
y: f32,
radius: f32,
r: f32,
g: f32,
b: f32,
a: f32,
) {
self.circles.extend_from_slice(&[x, y, radius, r, g, b, a, 32.0]);
}
/// Add a line gizmo.
/// 添加线条Gizmo。
pub fn add_line(
&mut self,
points: Vec<f32>,
r: f32,
g: f32,
b: f32,
a: f32,
closed: bool,
) {
self.lines.push(LineGizmo { points, r, g, b, a, closed });
}
/// Add a capsule outline gizmo.
/// 添加胶囊边框Gizmo。
///
/// Capsule is defined by center position, radius, half-height (distance from center to cap centers), and rotation.
/// 胶囊由中心位置、半径、半高度(从中心到端帽圆心的距离)和旋转定义。
pub fn add_capsule(
&mut self,
x: f32,
y: f32,
radius: f32,
half_height: f32,
rotation: f32,
r: f32,
g: f32,
b: f32,
a: f32,
) {
self.capsules.extend_from_slice(&[x, y, radius, half_height, rotation, r, g, b, a]);
}
/// Render axis indicator at bottom-left corner of the viewport.
/// 在视口左下角渲染坐标轴指示器。
///
/// This is drawn in screen space and is not affected by camera pan/zoom.
/// 这是在屏幕空间绘制的,不受相机平移/缩放影响。
@@ -204,72 +279,23 @@ impl GizmoRenderer {
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_with_i32(0, 2, WebGl2RenderingContext::FLOAT, false, 0, 0);
// Position in top-right corner (increased padding to prevent X label clipping)
// 位置在右上角(增加边距防止 X 标签被裁剪)
let padding_x = 70.0; // More padding on X for the label
let padding_y = 55.0;
let center_x = half_w - padding_x;
let center_y = half_h - padding_y;
let axis_length = 30.0; // Longer axes for better visibility
let arrow_size = 8.0;
let label_offset = 10.0;
let label_size = 4.0;
// Draw semi-transparent background circle for better visibility
// 绘制半透明背景圆以提高可见性
let bg_segments = 32;
let bg_radius = 45.0;
let mut bg_vertices = Vec::with_capacity((bg_segments + 1) * 2);
bg_vertices.push(center_x);
bg_vertices.push(center_y);
for i in 0..=bg_segments {
let angle = (i as f32 / bg_segments as f32) * std::f32::consts::PI * 2.0;
bg_vertices.push(center_x + bg_radius * angle.cos());
bg_vertices.push(center_y + bg_radius * angle.sin());
}
unsafe {
let array = js_sys::Float32Array::view(&bg_vertices);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.uniform4f(color_loc.as_ref(), 0.1, 0.1, 0.1, 0.7);
gl.draw_arrays(WebGl2RenderingContext::TRIANGLE_FAN, 0, (bg_segments + 2) as i32);
// Draw origin point (filled circle)
// 绘制原点(实心圆)
let origin_segments = 12;
let origin_radius = 3.0;
let mut origin_vertices = Vec::with_capacity((origin_segments + 1) * 2);
origin_vertices.push(center_x);
origin_vertices.push(center_y);
for i in 0..=origin_segments {
let angle = (i as f32 / origin_segments as f32) * std::f32::consts::PI * 2.0;
origin_vertices.push(center_x + origin_radius * angle.cos());
origin_vertices.push(center_y + origin_radius * angle.sin());
}
unsafe {
let array = js_sys::Float32Array::view(&origin_vertices);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.uniform4f(color_loc.as_ref(), 0.8, 0.8, 0.8, 1.0);
gl.draw_arrays(WebGl2RenderingContext::TRIANGLE_FAN, 0, (origin_segments + 2) as i32);
// Position in bottom-left corner
// 位置在左下角
let padding = 35.0;
let center_x = -half_w + padding;
let center_y = -half_h + padding;
let axis_length = 25.0;
let arrow_size = 6.0;
let label_offset = 8.0;
let label_size = 3.5;
// X axis (red, pointing right)
let x_end_x = center_x + axis_length;
let x_end_y = center_y;
// X axis line (thicker effect with multiple lines)
// X axis line
let x_axis = [
center_x + origin_radius, center_y,
center_x, center_y,
x_end_x - arrow_size * 0.3, x_end_y,
];
unsafe {
@@ -280,14 +306,14 @@ impl GizmoRenderer {
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.uniform4f(color_loc.as_ref(), 1.0, 0.4, 0.4, 1.0);
gl.uniform4f(color_loc.as_ref(), 0.9, 0.2, 0.2, 1.0);
gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 2);
// X arrow head (filled triangle)
let x_arrow = [
x_end_x, x_end_y,
x_end_x - arrow_size, x_end_y + arrow_size * 0.4,
x_end_x - arrow_size, x_end_y - arrow_size * 0.4,
x_end_x - arrow_size, x_end_y + arrow_size * 0.35,
x_end_x - arrow_size, x_end_y - arrow_size * 0.35,
];
unsafe {
let array = js_sys::Float32Array::view(&x_arrow);
@@ -324,7 +350,7 @@ impl GizmoRenderer {
// Y axis line
let y_axis = [
center_x, center_y + origin_radius,
center_x, center_y,
y_end_x, y_end_y - arrow_size * 0.3,
];
unsafe {
@@ -335,14 +361,14 @@ impl GizmoRenderer {
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.uniform4f(color_loc.as_ref(), 0.4, 1.0, 0.4, 1.0);
gl.uniform4f(color_loc.as_ref(), 0.26, 0.63, 0.28, 1.0);
gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 2);
// Y arrow head (filled triangle)
let y_arrow = [
y_end_x, y_end_y,
y_end_x - arrow_size * 0.4, y_end_y - arrow_size,
y_end_x + arrow_size * 0.4, y_end_y - arrow_size,
y_end_x - arrow_size * 0.35, y_end_y - arrow_size,
y_end_x + arrow_size * 0.35, y_end_y - arrow_size,
];
unsafe {
let array = js_sys::Float32Array::view(&y_arrow);
@@ -382,7 +408,7 @@ impl GizmoRenderer {
/// Render all pending gizmos.
/// 渲染所有待渲染的Gizmo。
pub fn render(&mut self, gl: &WebGl2RenderingContext, camera: &Camera2D) {
if self.rects.is_empty() {
if self.rects.is_empty() && self.circles.is_empty() && self.lines.is_empty() && self.capsules.is_empty() {
return;
}
@@ -398,7 +424,23 @@ impl GizmoRenderer {
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_with_i32(0, 2, WebGl2RenderingContext::FLOAT, false, 0, 0);
// Process each rectangle (12 floats per rect)
// Render rectangles
self.render_rects(gl, &color_loc, camera);
// Render circles
self.render_circles(gl, &color_loc);
// Render lines
self.render_lines(gl, &color_loc);
// Render capsules
self.render_capsules(gl, &color_loc);
gl.disable_vertex_attrib_array(0);
}
/// Render all pending rectangles.
fn render_rects(&self, gl: &WebGl2RenderingContext, color_loc: &Option<web_sys::WebGlUniformLocation>, camera: &Camera2D) {
let rect_stride = 12;
let rect_count = self.rects.len() / rect_stride;
@@ -417,7 +459,6 @@ impl GizmoRenderer {
let a = self.rects[offset + 10];
let show_handles = self.rects[offset + 11] > 0.5;
// Calculate transformed corners
let vertices = self.calculate_rect_vertices(x, y, width, height, rotation, origin_x, origin_y);
unsafe {
@@ -432,31 +473,157 @@ impl GizmoRenderer {
gl.uniform4f(color_loc.as_ref(), r, g, b, a);
gl.draw_arrays(WebGl2RenderingContext::LINE_LOOP, 0, 4);
// Only draw transform handles if explicitly requested
// 只有明确请求时才绘制变换手柄
if show_handles {
// Draw transform handles based on mode
match self.transform_mode {
TransformMode::Select => {
// Just the selection box (already drawn)
}
TransformMode::Select => {}
TransformMode::Move => {
// Draw move arrows at center
self.draw_move_handles(gl, &color_loc, x, y, rotation, camera);
self.draw_move_handles(gl, color_loc, x, y, rotation, camera);
}
TransformMode::Rotate => {
// Draw rotation circle
self.draw_rotate_handles(gl, &color_loc, x, y, width.max(height) * 0.6, camera);
self.draw_rotate_handles(gl, color_loc, x, y, width.max(height) * 0.6, camera);
}
TransformMode::Scale => {
// Draw scale handles at corners
self.draw_scale_handles(gl, &color_loc, x, y, width, height, rotation, origin_x, origin_y, camera);
self.draw_scale_handles(gl, color_loc, x, y, width, height, rotation, origin_x, origin_y, camera);
}
}
}
}
}
gl.disable_vertex_attrib_array(0);
/// Render all pending circles.
fn render_circles(&self, gl: &WebGl2RenderingContext, color_loc: &Option<web_sys::WebGlUniformLocation>) {
let circle_stride = 8;
let circle_count = self.circles.len() / circle_stride;
for i in 0..circle_count {
let offset = i * circle_stride;
let x = self.circles[offset];
let y = self.circles[offset + 1];
let radius = self.circles[offset + 2];
let r = self.circles[offset + 3];
let g = self.circles[offset + 4];
let b = self.circles[offset + 5];
let a = self.circles[offset + 6];
let segments = self.circles[offset + 7] as usize;
let mut vertices = Vec::with_capacity(segments * 2);
for j in 0..segments {
let angle = (j as f32 / segments as f32) * std::f32::consts::PI * 2.0;
vertices.push(x + radius * angle.cos());
vertices.push(y + radius * angle.sin());
}
unsafe {
let array = js_sys::Float32Array::view(&vertices);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.uniform4f(color_loc.as_ref(), r, g, b, a);
gl.draw_arrays(WebGl2RenderingContext::LINE_LOOP, 0, segments as i32);
}
}
/// Render all pending lines.
fn render_lines(&self, gl: &WebGl2RenderingContext, color_loc: &Option<web_sys::WebGlUniformLocation>) {
for line in &self.lines {
if line.points.len() < 4 {
continue;
}
unsafe {
let array = js_sys::Float32Array::view(&line.points);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.uniform4f(color_loc.as_ref(), line.r, line.g, line.b, line.a);
let point_count = (line.points.len() / 2) as i32;
if line.closed {
gl.draw_arrays(WebGl2RenderingContext::LINE_LOOP, 0, point_count);
} else {
gl.draw_arrays(WebGl2RenderingContext::LINE_STRIP, 0, point_count);
}
}
}
/// Render all pending capsules.
fn render_capsules(&self, gl: &WebGl2RenderingContext, color_loc: &Option<web_sys::WebGlUniformLocation>) {
let capsule_stride = 9;
let capsule_count = self.capsules.len() / capsule_stride;
let segments = 16;
for i in 0..capsule_count {
let offset = i * capsule_stride;
let cx = self.capsules[offset];
let cy = self.capsules[offset + 1];
let radius = self.capsules[offset + 2];
let half_height = self.capsules[offset + 3];
let rotation = self.capsules[offset + 4];
let r = self.capsules[offset + 5];
let g = self.capsules[offset + 6];
let b = self.capsules[offset + 7];
let a = self.capsules[offset + 8];
let cos_r = rotation.cos();
let sin_r = rotation.sin();
let mut vertices = Vec::with_capacity((segments * 2 + 4) * 2);
// Draw capsule in local space then rotate:
// - Top semicircle at y = +half_height, arc from angle 0 to PI
// - Right line from top-right to bottom-right
// - Bottom semicircle at y = -half_height, arc from angle PI to 2*PI
// - Left line from bottom-left to top-left (closed by LINE_LOOP)
// Top semicircle (arc curving upward)
for j in 0..=segments {
let angle = (j as f32 / segments as f32) * std::f32::consts::PI;
// Local coordinates: semicircle centered at (0, half_height)
// angle 0 -> (radius, half_height), angle PI -> (-radius, half_height)
// We want it to curve UP, so use cos for x, sin for y offset
let lx = radius * angle.cos();
let ly = half_height + radius * angle.sin();
// Rotate and translate to world
let wx = cx + lx * cos_r - ly * sin_r;
let wy = cy + lx * sin_r + ly * cos_r;
vertices.push(wx);
vertices.push(wy);
}
// Bottom semicircle (arc curving downward)
for j in 0..=segments {
let angle = (j as f32 / segments as f32) * std::f32::consts::PI;
// Local coordinates: semicircle centered at (0, -half_height)
// angle 0 -> (-radius, -half_height), angle PI -> (radius, -half_height)
// We want it to curve DOWN, so negate both cos and sin offset
let lx = -radius * angle.cos();
let ly = -half_height - radius * angle.sin();
// Rotate and translate to world
let wx = cx + lx * cos_r - ly * sin_r;
let wy = cy + lx * sin_r + ly * cos_r;
vertices.push(wx);
vertices.push(wy);
}
unsafe {
let array = js_sys::Float32Array::view(&vertices);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.uniform4f(color_loc.as_ref(), r, g, b, a);
gl.draw_arrays(WebGl2RenderingContext::LINE_LOOP, 0, ((segments + 1) * 2) as i32);
}
}
/// Set transform mode.