* refactor: reorganize package structure and decouple framework packages ## Package Structure Reorganization - Reorganized 55 packages into categorized subdirectories: - packages/framework/ - Generic framework (Laya/Cocos compatible) - packages/engine/ - ESEngine core modules - packages/rendering/ - Rendering modules (WASM dependent) - packages/physics/ - Physics modules - packages/streaming/ - World streaming - packages/network-ext/ - Network extensions - packages/editor/ - Editor framework and plugins - packages/rust/ - Rust WASM engine - packages/tools/ - Build tools and SDK ## Framework Package Decoupling - Decoupled behavior-tree and blueprint packages from ESEngine dependencies - Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent) - ESEngine-specific code moved to esengine/ subpath exports - Framework packages now usable with Cocos/Laya without ESEngine ## CI Configuration - Updated CI to only type-check and lint framework packages - Added type-check:framework and lint:framework scripts ## Breaking Changes - Package import paths changed due to directory reorganization - ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine') * fix: update es-engine file path after directory reorganization * docs: update README to focus on framework over engine * ci: only build framework packages, remove Rust/WASM dependencies * fix: remove esengine subpath from behavior-tree and blueprint builds ESEngine integration code will only be available in full engine builds. Framework packages are now purely engine-agnostic. * fix: move network-protocols to framework, build both in CI * fix: update workflow paths from packages/core to packages/framework/core * fix: exclude esengine folder from type-check in behavior-tree and blueprint * fix: update network tsconfig references to new paths * fix: add test:ci:framework to only test framework packages in CI * fix: only build core and math npm packages in CI * fix: exclude test files from CodeQL and fix string escaping security issue
349 lines
13 KiB
Rust
349 lines
13 KiB
Rust
//! Gizmo renderer for editor overlays.
|
|
|
|
use es_engine_shared::{
|
|
traits::backend::{GraphicsBackend, BufferUsage},
|
|
types::{
|
|
handle::{ShaderHandle, BufferHandle, VertexArrayHandle},
|
|
vertex::{VertexLayout, VertexAttribute, VertexAttributeType},
|
|
blend::BlendMode,
|
|
},
|
|
Vec4, Mat3,
|
|
};
|
|
use super::camera::Camera2D;
|
|
use std::f32::consts::PI;
|
|
|
|
const VERTEX_SHADER: &str = r#"#version 300 es
|
|
precision highp float;
|
|
layout(location = 0) in vec2 a_position;
|
|
uniform mat3 u_projection;
|
|
void main() {
|
|
vec3 pos = u_projection * vec3(a_position, 1.0);
|
|
gl_Position = vec4(pos.xy, 0.0, 1.0);
|
|
}
|
|
"#;
|
|
|
|
const FRAGMENT_SHADER: &str = r#"#version 300 es
|
|
precision highp float;
|
|
uniform vec4 u_color;
|
|
out vec4 fragColor;
|
|
void main() {
|
|
fragColor = u_color;
|
|
}
|
|
"#;
|
|
|
|
const X_AXIS_COLOR: Vec4 = Vec4::new(1.0, 0.3, 0.3, 1.0);
|
|
const Y_AXIS_COLOR: Vec4 = Vec4::new(0.3, 1.0, 0.3, 1.0);
|
|
const ROTATE_COLOR: Vec4 = Vec4::new(0.3, 0.6, 1.0, 1.0);
|
|
const SCALE_COLOR: Vec4 = Vec4::new(1.0, 0.8, 0.2, 1.0);
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
|
pub enum TransformMode {
|
|
#[default]
|
|
Select,
|
|
Move,
|
|
Rotate,
|
|
Scale,
|
|
}
|
|
|
|
pub struct GizmoRenderer {
|
|
shader: ShaderHandle,
|
|
vbo: BufferHandle,
|
|
vao: VertexArrayHandle,
|
|
rects: Vec<RectGizmo>,
|
|
circles: Vec<CircleGizmo>,
|
|
lines: Vec<LineGizmo>,
|
|
capsules: Vec<CapsuleGizmo>,
|
|
transform_mode: TransformMode,
|
|
}
|
|
|
|
struct RectGizmo {
|
|
x: f32, y: f32, width: f32, height: f32,
|
|
rotation: f32, origin_x: f32, origin_y: f32,
|
|
color: Vec4, show_handles: bool,
|
|
}
|
|
|
|
struct CircleGizmo {
|
|
x: f32, y: f32, radius: f32, color: Vec4, segments: u32,
|
|
}
|
|
|
|
struct LineGizmo {
|
|
points: Vec<f32>, color: Vec4, closed: bool,
|
|
}
|
|
|
|
struct CapsuleGizmo {
|
|
x: f32, y: f32, radius: f32, half_height: f32, rotation: f32, color: Vec4,
|
|
}
|
|
|
|
const MAX_GIZMO_VERTICES: usize = 4000;
|
|
|
|
impl GizmoRenderer {
|
|
pub fn new(backend: &mut impl GraphicsBackend) -> Result<Self, String> {
|
|
let shader = backend.compile_shader(VERTEX_SHADER, FRAGMENT_SHADER)
|
|
.map_err(|e| format!("Gizmo shader: {:?}", e))?;
|
|
|
|
let layout = VertexLayout {
|
|
attributes: vec![VertexAttribute {
|
|
name: "a_position".into(),
|
|
attr_type: VertexAttributeType::Float2,
|
|
offset: 0,
|
|
normalized: false,
|
|
}],
|
|
stride: 8,
|
|
};
|
|
|
|
let buffer_size = MAX_GIZMO_VERTICES * 2 * 4;
|
|
let vbo = backend.create_vertex_buffer_sized(buffer_size, BufferUsage::Dynamic)
|
|
.map_err(|e| format!("Gizmo VBO: {:?}", e))?;
|
|
let vao = backend.create_vertex_array(vbo, None, &layout)
|
|
.map_err(|e| format!("Gizmo VAO: {:?}", e))?;
|
|
|
|
Ok(Self {
|
|
shader, vbo, vao,
|
|
rects: Vec::new(),
|
|
circles: Vec::new(),
|
|
lines: Vec::new(),
|
|
capsules: Vec::new(),
|
|
transform_mode: TransformMode::default(),
|
|
})
|
|
}
|
|
|
|
pub fn clear(&mut self) {
|
|
self.rects.clear();
|
|
self.circles.clear();
|
|
self.lines.clear();
|
|
self.capsules.clear();
|
|
}
|
|
|
|
pub fn add_rect(&mut self, x: f32, y: f32, width: f32, height: f32, rotation: f32,
|
|
origin_x: f32, origin_y: f32, r: f32, g: f32, b: f32, a: f32, show_handles: bool) {
|
|
self.rects.push(RectGizmo {
|
|
x, y, width, height, rotation, origin_x, origin_y,
|
|
color: Vec4::new(r, g, b, a), show_handles,
|
|
});
|
|
}
|
|
|
|
pub fn add_circle(&mut self, x: f32, y: f32, radius: f32, r: f32, g: f32, b: f32, a: f32) {
|
|
self.circles.push(CircleGizmo { x, y, radius, color: Vec4::new(r, g, b, a), segments: 32 });
|
|
}
|
|
|
|
pub fn add_line(&mut self, points: Vec<f32>, r: f32, g: f32, b: f32, a: f32, closed: bool) {
|
|
self.lines.push(LineGizmo { points, color: Vec4::new(r, g, b, a), closed });
|
|
}
|
|
|
|
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.push(CapsuleGizmo { x, y, radius, half_height, rotation, color: Vec4::new(r, g, b, a) });
|
|
}
|
|
|
|
pub fn set_transform_mode(&mut self, mode: TransformMode) {
|
|
self.transform_mode = mode;
|
|
}
|
|
|
|
pub fn get_transform_mode(&self) -> TransformMode {
|
|
self.transform_mode
|
|
}
|
|
|
|
pub fn render(&mut self, backend: &mut impl GraphicsBackend, camera: &Camera2D) {
|
|
if self.rects.is_empty() && self.circles.is_empty() && self.lines.is_empty() && self.capsules.is_empty() {
|
|
return;
|
|
}
|
|
|
|
backend.bind_shader(self.shader).ok();
|
|
backend.set_uniform_mat3("u_projection", &camera.projection_matrix()).ok();
|
|
backend.set_blend_mode(BlendMode::Alpha);
|
|
|
|
self.render_rects(backend, camera);
|
|
self.render_circles(backend);
|
|
self.render_lines(backend);
|
|
self.render_capsules(backend);
|
|
}
|
|
|
|
pub fn render_axis_indicator(&mut self, backend: &mut impl GraphicsBackend, width: f32, height: f32) {
|
|
if width < 100.0 || height < 100.0 {
|
|
return;
|
|
}
|
|
|
|
backend.bind_shader(self.shader).ok();
|
|
|
|
let half_w = width / 2.0;
|
|
let half_h = height / 2.0;
|
|
let projection = Mat3::from_cols_array(&[
|
|
1.0 / half_w, 0.0, 0.0,
|
|
0.0, 1.0 / half_h, 0.0,
|
|
0.0, 0.0, 1.0,
|
|
]);
|
|
backend.set_uniform_mat3("u_projection", &projection).ok();
|
|
backend.set_blend_mode(BlendMode::Alpha);
|
|
|
|
let padding = 35.0;
|
|
let cx = -half_w + padding;
|
|
let cy = -half_h + padding;
|
|
let axis_len = 25.0;
|
|
let arrow = 6.0;
|
|
let label_off = 8.0;
|
|
let label_sz = 3.5;
|
|
|
|
// X axis
|
|
let x_end = cx + axis_len;
|
|
self.upload_and_draw_lines(backend, &[cx, cy, x_end - arrow * 0.3, cy], X_AXIS_COLOR);
|
|
self.upload_and_draw(backend, &[x_end, cy, x_end - arrow, cy + arrow * 0.35, x_end - arrow, cy - arrow * 0.35], X_AXIS_COLOR);
|
|
let lx = x_end + label_off;
|
|
self.upload_and_draw_lines(backend, &[lx - label_sz, cy + label_sz, lx + label_sz, cy - label_sz,
|
|
lx - label_sz, cy - label_sz, lx + label_sz, cy + label_sz], X_AXIS_COLOR);
|
|
|
|
// Y axis
|
|
let y_end = cy + axis_len;
|
|
self.upload_and_draw_lines(backend, &[cx, cy, cx, y_end - arrow * 0.3], Y_AXIS_COLOR);
|
|
self.upload_and_draw(backend, &[cx, y_end, cx - arrow * 0.35, y_end - arrow, cx + arrow * 0.35, y_end - arrow], Y_AXIS_COLOR);
|
|
let ly = y_end + label_off;
|
|
self.upload_and_draw_lines(backend, &[cx - label_sz, ly + label_sz, cx, ly, cx + label_sz, ly + label_sz, cx, ly,
|
|
cx, ly, cx, ly - label_sz * 0.8], Y_AXIS_COLOR);
|
|
}
|
|
|
|
fn render_rects(&mut self, backend: &mut impl GraphicsBackend, camera: &Camera2D) {
|
|
let rects: Vec<_> = std::mem::take(&mut self.rects);
|
|
for rect in &rects {
|
|
let verts = Self::calc_rect_vertices(rect.x, rect.y, rect.width, rect.height,
|
|
rect.rotation, rect.origin_x, rect.origin_y);
|
|
self.upload_and_draw_line_loop(backend, &verts, rect.color);
|
|
|
|
if rect.show_handles {
|
|
match self.transform_mode {
|
|
TransformMode::Select => {}
|
|
TransformMode::Move => self.draw_move_handles(backend, rect.x, rect.y, rect.rotation, camera),
|
|
TransformMode::Rotate => self.draw_rotate_handles(backend, rect.x, rect.y, rect.width.max(rect.height) * 0.6),
|
|
TransformMode::Scale => self.draw_scale_handles(backend, &verts, camera),
|
|
}
|
|
}
|
|
}
|
|
self.rects = rects;
|
|
}
|
|
|
|
fn render_circles(&mut self, backend: &mut impl GraphicsBackend) {
|
|
let circles: Vec<_> = std::mem::take(&mut self.circles);
|
|
for circle in &circles {
|
|
let verts = Self::build_circle(circle.x, circle.y, circle.radius, circle.segments);
|
|
self.upload_and_draw_line_loop(backend, &verts, circle.color);
|
|
}
|
|
self.circles = circles;
|
|
}
|
|
|
|
fn render_lines(&mut self, backend: &mut impl GraphicsBackend) {
|
|
let lines: Vec<_> = std::mem::take(&mut self.lines);
|
|
for line in &lines {
|
|
if line.points.len() < 4 { continue; }
|
|
if line.closed {
|
|
self.upload_and_draw_line_loop(backend, &line.points, line.color);
|
|
} else {
|
|
self.upload_and_draw_line_strip(backend, &line.points, line.color);
|
|
}
|
|
}
|
|
self.lines = lines;
|
|
}
|
|
|
|
fn render_capsules(&mut self, backend: &mut impl GraphicsBackend) {
|
|
const SEGMENTS: usize = 16;
|
|
let capsules: Vec<_> = std::mem::take(&mut self.capsules);
|
|
for cap in &capsules {
|
|
let (cos_r, sin_r) = (cap.rotation.cos(), cap.rotation.sin());
|
|
let mut verts = Vec::with_capacity((SEGMENTS * 2 + 2) * 2);
|
|
|
|
for j in 0..=SEGMENTS {
|
|
let angle = (j as f32 / SEGMENTS as f32) * PI;
|
|
let (lx, ly) = (cap.radius * angle.cos(), cap.half_height + cap.radius * angle.sin());
|
|
verts.push(cap.x + lx * cos_r - ly * sin_r);
|
|
verts.push(cap.y + lx * sin_r + ly * cos_r);
|
|
}
|
|
for j in 0..=SEGMENTS {
|
|
let angle = (j as f32 / SEGMENTS as f32) * PI;
|
|
let (lx, ly) = (-cap.radius * angle.cos(), -cap.half_height - cap.radius * angle.sin());
|
|
verts.push(cap.x + lx * cos_r - ly * sin_r);
|
|
verts.push(cap.y + lx * sin_r + ly * cos_r);
|
|
}
|
|
self.upload_and_draw_line_loop(backend, &verts, cap.color);
|
|
}
|
|
self.capsules = capsules;
|
|
}
|
|
|
|
fn draw_move_handles(&mut self, backend: &mut impl GraphicsBackend, x: f32, y: f32, rotation: f32, camera: &Camera2D) {
|
|
let len = 50.0 / camera.zoom;
|
|
let head = 10.0 / camera.zoom;
|
|
let (cos, sin) = (rotation.cos(), rotation.sin());
|
|
|
|
let (xe, ye) = (x + len * cos, y + len * sin);
|
|
let x_arrow = [x, y, xe, ye,
|
|
xe - head * cos + head * 0.3 * sin, ye - head * sin - head * 0.3 * cos, xe, ye,
|
|
xe - head * cos - head * 0.3 * sin, ye - head * sin + head * 0.3 * cos];
|
|
self.upload_and_draw_line_strip(backend, &x_arrow, X_AXIS_COLOR);
|
|
|
|
let (xe2, ye2) = (x - len * sin, y + len * cos);
|
|
let y_arrow = [x, y, xe2, ye2,
|
|
xe2 + head * sin + head * 0.3 * cos, ye2 - head * cos + head * 0.3 * sin, xe2, ye2,
|
|
xe2 + head * sin - head * 0.3 * cos, ye2 - head * cos - head * 0.3 * sin];
|
|
self.upload_and_draw_line_strip(backend, &y_arrow, Y_AXIS_COLOR);
|
|
}
|
|
|
|
fn draw_rotate_handles(&mut self, backend: &mut impl GraphicsBackend, x: f32, y: f32, radius: f32) {
|
|
let verts = Self::build_circle(x, y, radius, 32);
|
|
self.upload_and_draw_line_loop(backend, &verts, ROTATE_COLOR);
|
|
}
|
|
|
|
fn draw_scale_handles(&mut self, backend: &mut impl GraphicsBackend, corners: &[f32], camera: &Camera2D) {
|
|
let sz = 6.0 / camera.zoom;
|
|
for i in 0..4 {
|
|
let (cx, cy) = (corners[i * 2], corners[i * 2 + 1]);
|
|
let sq = [cx - sz, cy - sz, cx + sz, cy - sz, cx + sz, cy + sz, cx - sz, cy + sz];
|
|
self.upload_and_draw_line_loop(backend, &sq, SCALE_COLOR);
|
|
}
|
|
}
|
|
|
|
fn upload_and_draw(&mut self, backend: &mut impl GraphicsBackend, verts: &[f32], color: Vec4) {
|
|
backend.update_buffer(self.vbo, 0, bytemuck::cast_slice(verts)).ok();
|
|
backend.set_uniform_vec4("u_color", color).ok();
|
|
backend.draw(self.vao, (verts.len() / 2) as u32, 0).ok();
|
|
}
|
|
|
|
fn upload_and_draw_lines(&mut self, backend: &mut impl GraphicsBackend, verts: &[f32], color: Vec4) {
|
|
backend.update_buffer(self.vbo, 0, bytemuck::cast_slice(verts)).ok();
|
|
backend.set_uniform_vec4("u_color", color).ok();
|
|
backend.draw_lines(self.vao, (verts.len() / 2) as u32, 0).ok();
|
|
}
|
|
|
|
fn upload_and_draw_line_loop(&mut self, backend: &mut impl GraphicsBackend, verts: &[f32], color: Vec4) {
|
|
backend.update_buffer(self.vbo, 0, bytemuck::cast_slice(verts)).ok();
|
|
backend.set_uniform_vec4("u_color", color).ok();
|
|
backend.draw_line_loop(self.vao, (verts.len() / 2) as u32, 0).ok();
|
|
}
|
|
|
|
fn upload_and_draw_line_strip(&mut self, backend: &mut impl GraphicsBackend, verts: &[f32], color: Vec4) {
|
|
backend.update_buffer(self.vbo, 0, bytemuck::cast_slice(verts)).ok();
|
|
backend.set_uniform_vec4("u_color", color).ok();
|
|
backend.draw_line_strip(self.vao, (verts.len() / 2) as u32, 0).ok();
|
|
}
|
|
|
|
fn calc_rect_vertices(x: f32, y: f32, w: f32, h: f32, rot: f32, ox: f32, oy: f32) -> [f32; 8] {
|
|
let (cos, sin) = (rot.cos(), rot.sin());
|
|
let (oxx, oyy) = (ox * w, oy * h);
|
|
let corners = [(-oxx, h - oyy), (w - oxx, h - oyy), (w - oxx, -oyy), (-oxx, -oyy)];
|
|
let mut out = [0.0f32; 8];
|
|
for (i, (lx, ly)) in corners.iter().enumerate() {
|
|
out[i * 2] = lx * cos - ly * sin + x;
|
|
out[i * 2 + 1] = lx * sin + ly * cos + y;
|
|
}
|
|
out
|
|
}
|
|
|
|
fn build_circle(x: f32, y: f32, r: f32, segments: u32) -> Vec<f32> {
|
|
(0..segments).flat_map(|i| {
|
|
let angle = (i as f32 / segments as f32) * PI * 2.0;
|
|
[x + r * angle.cos(), y + r * angle.sin()]
|
|
}).collect()
|
|
}
|
|
|
|
pub fn destroy(self, backend: &mut impl GraphicsBackend) {
|
|
backend.destroy_vertex_array(self.vao);
|
|
backend.destroy_buffer(self.vbo);
|
|
backend.destroy_shader(self.shader);
|
|
}
|
|
}
|