2025-11-23 14:49:37 +08:00
|
|
|
|
//! Gizmo renderer for editor overlays.
|
|
|
|
|
|
//! 编辑器叠加层的Gizmo渲染器。
|
|
|
|
|
|
|
|
|
|
|
|
use web_sys::{WebGl2RenderingContext, WebGlBuffer, WebGlProgram};
|
|
|
|
|
|
use crate::core::error::{Result, EngineError};
|
|
|
|
|
|
use super::camera::Camera2D;
|
|
|
|
|
|
|
|
|
|
|
|
const GIZMO_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 GIZMO_FRAGMENT_SHADER: &str = r#"#version 300 es
|
|
|
|
|
|
precision highp float;
|
|
|
|
|
|
|
|
|
|
|
|
uniform vec4 u_color;
|
|
|
|
|
|
|
|
|
|
|
|
out vec4 fragColor;
|
|
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
|
fragColor = u_color;
|
|
|
|
|
|
}
|
|
|
|
|
|
"#;
|
|
|
|
|
|
|
|
|
|
|
|
/// Transform tool mode.
|
|
|
|
|
|
/// 变换工具模式。
|
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
|
|
|
|
pub enum TransformMode {
|
|
|
|
|
|
/// Selection mode - show bounds only
|
|
|
|
|
|
Select,
|
|
|
|
|
|
/// Move mode - show translation arrows
|
|
|
|
|
|
Move,
|
|
|
|
|
|
/// Rotate mode - show rotation circle
|
|
|
|
|
|
Rotate,
|
|
|
|
|
|
/// Scale mode - show scale handles
|
|
|
|
|
|
Scale,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Default for TransformMode {
|
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
|
TransformMode::Select
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Gizmo renderer for drawing editor overlays like selection bounds.
|
|
|
|
|
|
/// 用于绘制编辑器叠加层(如选择边界)的Gizmo渲染器。
|
|
|
|
|
|
pub struct GizmoRenderer {
|
|
|
|
|
|
program: WebGlProgram,
|
|
|
|
|
|
vertex_buffer: WebGlBuffer,
|
|
|
|
|
|
/// Pending rectangle data: [x, y, width, height, rotation, origin_x, origin_y, r, g, b, a, show_handles]
|
|
|
|
|
|
/// 待渲染的矩形数据
|
|
|
|
|
|
rects: Vec<f32>,
|
|
|
|
|
|
/// Current transform mode
|
|
|
|
|
|
transform_mode: TransformMode,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl GizmoRenderer {
|
|
|
|
|
|
/// Create a new gizmo renderer.
|
|
|
|
|
|
/// 创建新的Gizmo渲染器。
|
|
|
|
|
|
pub fn new(gl: &WebGl2RenderingContext) -> Result<Self> {
|
|
|
|
|
|
let program = Self::create_program(gl)?;
|
|
|
|
|
|
let vertex_buffer = gl.create_buffer()
|
|
|
|
|
|
.ok_or(EngineError::BufferCreationFailed)?;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
|
|
program,
|
|
|
|
|
|
vertex_buffer,
|
|
|
|
|
|
rects: Vec::new(),
|
|
|
|
|
|
transform_mode: TransformMode::default(),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn create_program(gl: &WebGl2RenderingContext) -> Result<WebGlProgram> {
|
|
|
|
|
|
let vert_shader = gl.create_shader(WebGl2RenderingContext::VERTEX_SHADER)
|
|
|
|
|
|
.ok_or_else(|| EngineError::ShaderCompileFailed("Failed to create vertex shader".into()))?;
|
|
|
|
|
|
gl.shader_source(&vert_shader, GIZMO_VERTEX_SHADER);
|
|
|
|
|
|
gl.compile_shader(&vert_shader);
|
|
|
|
|
|
|
|
|
|
|
|
if !gl.get_shader_parameter(&vert_shader, WebGl2RenderingContext::COMPILE_STATUS)
|
|
|
|
|
|
.as_bool()
|
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
|
{
|
|
|
|
|
|
let log = gl.get_shader_info_log(&vert_shader).unwrap_or_default();
|
|
|
|
|
|
return Err(EngineError::ShaderCompileFailed(format!("Gizmo vertex shader: {}", log)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let frag_shader = gl.create_shader(WebGl2RenderingContext::FRAGMENT_SHADER)
|
|
|
|
|
|
.ok_or_else(|| EngineError::ShaderCompileFailed("Failed to create fragment shader".into()))?;
|
|
|
|
|
|
gl.shader_source(&frag_shader, GIZMO_FRAGMENT_SHADER);
|
|
|
|
|
|
gl.compile_shader(&frag_shader);
|
|
|
|
|
|
|
|
|
|
|
|
if !gl.get_shader_parameter(&frag_shader, WebGl2RenderingContext::COMPILE_STATUS)
|
|
|
|
|
|
.as_bool()
|
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
|
{
|
|
|
|
|
|
let log = gl.get_shader_info_log(&frag_shader).unwrap_or_default();
|
|
|
|
|
|
return Err(EngineError::ShaderCompileFailed(format!("Gizmo fragment shader: {}", log)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let program = gl.create_program()
|
|
|
|
|
|
.ok_or_else(|| EngineError::ProgramLinkFailed("Failed to create gizmo program".into()))?;
|
|
|
|
|
|
gl.attach_shader(&program, &vert_shader);
|
|
|
|
|
|
gl.attach_shader(&program, &frag_shader);
|
|
|
|
|
|
gl.link_program(&program);
|
|
|
|
|
|
|
|
|
|
|
|
if !gl.get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS)
|
|
|
|
|
|
.as_bool()
|
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
|
{
|
|
|
|
|
|
let log = gl.get_program_info_log(&program).unwrap_or_default();
|
|
|
|
|
|
return Err(EngineError::ProgramLinkFailed(format!("Gizmo program: {}", log)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
gl.delete_shader(Some(&vert_shader));
|
|
|
|
|
|
gl.delete_shader(Some(&frag_shader));
|
|
|
|
|
|
|
|
|
|
|
|
Ok(program)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Clear all pending gizmos.
|
|
|
|
|
|
/// 清空所有待渲染的Gizmo。
|
|
|
|
|
|
pub fn clear(&mut self) {
|
|
|
|
|
|
self.rects.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Add a rectangle outline gizmo.
|
|
|
|
|
|
/// 添加矩形边框Gizmo。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Arguments | 参数
|
|
|
|
|
|
/// * `x` - Center X position | 中心X位置
|
|
|
|
|
|
/// * `y` - Center Y position | 中心Y位置
|
|
|
|
|
|
/// * `width` - Rectangle width | 矩形宽度
|
|
|
|
|
|
/// * `height` - Rectangle height | 矩形高度
|
|
|
|
|
|
/// * `rotation` - Rotation in radians | 旋转角度(弧度)
|
|
|
|
|
|
/// * `origin_x` - Origin X (0-1) | 原点X (0-1)
|
|
|
|
|
|
/// * `origin_y` - Origin Y (0-1) | 原点Y (0-1)
|
|
|
|
|
|
/// * `r`, `g`, `b`, `a` - Color | 颜色
|
|
|
|
|
|
/// * `show_handles` - Whether to show transform handles | 是否显示变换手柄
|
|
|
|
|
|
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.extend_from_slice(&[
|
|
|
|
|
|
x, y, width, height, rotation, origin_x, origin_y, r, g, b, a, if show_handles { 1.0 } else { 0.0 }
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-27 20:42:46 +08:00
|
|
|
|
/// Render axis indicator at top-right corner of the viewport.
|
|
|
|
|
|
/// 在视口右上角渲染坐标轴指示器。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This is drawn in screen space and is not affected by camera pan/zoom.
|
|
|
|
|
|
/// 这是在屏幕空间绘制的,不受相机平移/缩放影响。
|
|
|
|
|
|
pub fn render_axis_indicator(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
gl: &WebGl2RenderingContext,
|
|
|
|
|
|
viewport_width: f32,
|
|
|
|
|
|
viewport_height: f32,
|
|
|
|
|
|
) {
|
|
|
|
|
|
// Skip if viewport is too small
|
|
|
|
|
|
if viewport_width < 100.0 || viewport_height < 100.0 {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
gl.use_program(Some(&self.program));
|
|
|
|
|
|
|
|
|
|
|
|
// Disable depth test for screen-space UI
|
|
|
|
|
|
gl.disable(WebGl2RenderingContext::DEPTH_TEST);
|
|
|
|
|
|
|
|
|
|
|
|
// Create orthographic projection for screen space (NDC: -1 to 1)
|
|
|
|
|
|
let half_w = viewport_width / 2.0;
|
|
|
|
|
|
let half_h = viewport_height / 2.0;
|
|
|
|
|
|
|
|
|
|
|
|
let projection = [
|
|
|
|
|
|
1.0 / half_w, 0.0, 0.0,
|
|
|
|
|
|
0.0, 1.0 / half_h, 0.0,
|
|
|
|
|
|
0.0, 0.0, 1.0,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
let proj_loc = gl.get_uniform_location(&self.program, "u_projection");
|
|
|
|
|
|
gl.uniform_matrix3fv_with_f32_array(proj_loc.as_ref(), false, &projection);
|
|
|
|
|
|
|
|
|
|
|
|
let color_loc = gl.get_uniform_location(&self.program, "u_color");
|
|
|
|
|
|
|
|
|
|
|
|
gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&self.vertex_buffer));
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
let x_axis = [
|
|
|
|
|
|
center_x + origin_radius, center_y,
|
|
|
|
|
|
x_end_x - arrow_size * 0.3, x_end_y,
|
|
|
|
|
|
];
|
|
|
|
|
|
unsafe {
|
|
|
|
|
|
let array = js_sys::Float32Array::view(&x_axis);
|
|
|
|
|
|
gl.buffer_data_with_array_buffer_view(
|
|
|
|
|
|
WebGl2RenderingContext::ARRAY_BUFFER,
|
|
|
|
|
|
&array,
|
|
|
|
|
|
WebGl2RenderingContext::DYNAMIC_DRAW,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
gl.uniform4f(color_loc.as_ref(), 1.0, 0.4, 0.4, 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,
|
|
|
|
|
|
];
|
|
|
|
|
|
unsafe {
|
|
|
|
|
|
let array = js_sys::Float32Array::view(&x_arrow);
|
|
|
|
|
|
gl.buffer_data_with_array_buffer_view(
|
|
|
|
|
|
WebGl2RenderingContext::ARRAY_BUFFER,
|
|
|
|
|
|
&array,
|
|
|
|
|
|
WebGl2RenderingContext::DYNAMIC_DRAW,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
gl.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, 3);
|
|
|
|
|
|
|
|
|
|
|
|
// X label
|
|
|
|
|
|
let lx = x_end_x + label_offset;
|
|
|
|
|
|
let ly = x_end_y;
|
|
|
|
|
|
let x_label = [
|
|
|
|
|
|
lx - label_size, ly + label_size,
|
|
|
|
|
|
lx + label_size, ly - label_size,
|
|
|
|
|
|
lx - label_size, ly - label_size,
|
|
|
|
|
|
lx + label_size, ly + label_size,
|
|
|
|
|
|
];
|
|
|
|
|
|
unsafe {
|
|
|
|
|
|
let array = js_sys::Float32Array::view(&x_label);
|
|
|
|
|
|
gl.buffer_data_with_array_buffer_view(
|
|
|
|
|
|
WebGl2RenderingContext::ARRAY_BUFFER,
|
|
|
|
|
|
&array,
|
|
|
|
|
|
WebGl2RenderingContext::DYNAMIC_DRAW,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 4);
|
|
|
|
|
|
|
|
|
|
|
|
// Y axis (green, pointing up)
|
|
|
|
|
|
let y_end_x = center_x;
|
|
|
|
|
|
let y_end_y = center_y + axis_length;
|
|
|
|
|
|
|
|
|
|
|
|
// Y axis line
|
|
|
|
|
|
let y_axis = [
|
|
|
|
|
|
center_x, center_y + origin_radius,
|
|
|
|
|
|
y_end_x, y_end_y - arrow_size * 0.3,
|
|
|
|
|
|
];
|
|
|
|
|
|
unsafe {
|
|
|
|
|
|
let array = js_sys::Float32Array::view(&y_axis);
|
|
|
|
|
|
gl.buffer_data_with_array_buffer_view(
|
|
|
|
|
|
WebGl2RenderingContext::ARRAY_BUFFER,
|
|
|
|
|
|
&array,
|
|
|
|
|
|
WebGl2RenderingContext::DYNAMIC_DRAW,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
gl.uniform4f(color_loc.as_ref(), 0.4, 1.0, 0.4, 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,
|
|
|
|
|
|
];
|
|
|
|
|
|
unsafe {
|
|
|
|
|
|
let array = js_sys::Float32Array::view(&y_arrow);
|
|
|
|
|
|
gl.buffer_data_with_array_buffer_view(
|
|
|
|
|
|
WebGl2RenderingContext::ARRAY_BUFFER,
|
|
|
|
|
|
&array,
|
|
|
|
|
|
WebGl2RenderingContext::DYNAMIC_DRAW,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
gl.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, 3);
|
|
|
|
|
|
|
|
|
|
|
|
// Y label
|
|
|
|
|
|
let lx = y_end_x;
|
|
|
|
|
|
let ly = y_end_y + label_offset;
|
|
|
|
|
|
let y_label = [
|
|
|
|
|
|
lx - label_size, ly + label_size,
|
|
|
|
|
|
lx, ly,
|
|
|
|
|
|
lx + label_size, ly + label_size,
|
|
|
|
|
|
lx, ly,
|
|
|
|
|
|
lx, ly,
|
|
|
|
|
|
lx, ly - label_size * 0.8,
|
|
|
|
|
|
];
|
|
|
|
|
|
unsafe {
|
|
|
|
|
|
let array = js_sys::Float32Array::view(&y_label);
|
|
|
|
|
|
gl.buffer_data_with_array_buffer_view(
|
|
|
|
|
|
WebGl2RenderingContext::ARRAY_BUFFER,
|
|
|
|
|
|
&array,
|
|
|
|
|
|
WebGl2RenderingContext::DYNAMIC_DRAW,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 6);
|
|
|
|
|
|
|
|
|
|
|
|
// Cleanup
|
|
|
|
|
|
gl.disable_vertex_attrib_array(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-23 14:49:37 +08:00
|
|
|
|
/// Render all pending gizmos.
|
|
|
|
|
|
/// 渲染所有待渲染的Gizmo。
|
|
|
|
|
|
pub fn render(&mut self, gl: &WebGl2RenderingContext, camera: &Camera2D) {
|
|
|
|
|
|
if self.rects.is_empty() {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
gl.use_program(Some(&self.program));
|
|
|
|
|
|
|
|
|
|
|
|
let projection = camera.projection_matrix();
|
|
|
|
|
|
let proj_loc = gl.get_uniform_location(&self.program, "u_projection");
|
|
|
|
|
|
gl.uniform_matrix3fv_with_f32_array(proj_loc.as_ref(), false, &projection.to_cols_array());
|
|
|
|
|
|
|
|
|
|
|
|
let color_loc = gl.get_uniform_location(&self.program, "u_color");
|
|
|
|
|
|
|
|
|
|
|
|
gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&self.vertex_buffer));
|
|
|
|
|
|
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)
|
|
|
|
|
|
let rect_stride = 12;
|
|
|
|
|
|
let rect_count = self.rects.len() / rect_stride;
|
|
|
|
|
|
|
|
|
|
|
|
for i in 0..rect_count {
|
|
|
|
|
|
let offset = i * rect_stride;
|
|
|
|
|
|
let x = self.rects[offset];
|
|
|
|
|
|
let y = self.rects[offset + 1];
|
|
|
|
|
|
let width = self.rects[offset + 2];
|
|
|
|
|
|
let height = self.rects[offset + 3];
|
|
|
|
|
|
let rotation = self.rects[offset + 4];
|
|
|
|
|
|
let origin_x = self.rects[offset + 5];
|
|
|
|
|
|
let origin_y = self.rects[offset + 6];
|
|
|
|
|
|
let r = self.rects[offset + 7];
|
|
|
|
|
|
let g = self.rects[offset + 8];
|
|
|
|
|
|
let b = self.rects[offset + 9];
|
|
|
|
|
|
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 {
|
|
|
|
|
|
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, 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::Move => {
|
|
|
|
|
|
// Draw move arrows at center
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
TransformMode::Scale => {
|
|
|
|
|
|
// Draw scale handles at corners
|
|
|
|
|
|
self.draw_scale_handles(gl, &color_loc, x, y, width, height, rotation, origin_x, origin_y, camera);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
gl.disable_vertex_attrib_array(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Set transform mode.
|
|
|
|
|
|
/// 设置变换模式。
|
|
|
|
|
|
pub fn set_transform_mode(&mut self, mode: TransformMode) {
|
|
|
|
|
|
self.transform_mode = mode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Get transform mode.
|
|
|
|
|
|
/// 获取变换模式。
|
|
|
|
|
|
pub fn get_transform_mode(&self) -> TransformMode {
|
|
|
|
|
|
self.transform_mode
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Draw move handles (arrows).
|
|
|
|
|
|
/// 绘制移动手柄(箭头)。
|
|
|
|
|
|
fn draw_move_handles(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
gl: &WebGl2RenderingContext,
|
|
|
|
|
|
color_loc: &Option<web_sys::WebGlUniformLocation>,
|
|
|
|
|
|
x: f32,
|
|
|
|
|
|
y: f32,
|
|
|
|
|
|
rotation: f32,
|
|
|
|
|
|
camera: &Camera2D,
|
|
|
|
|
|
) {
|
|
|
|
|
|
let arrow_length = 50.0 / camera.zoom;
|
|
|
|
|
|
let arrow_head = 10.0 / camera.zoom;
|
|
|
|
|
|
let cos = rotation.cos();
|
|
|
|
|
|
let sin = rotation.sin();
|
|
|
|
|
|
|
|
|
|
|
|
// X axis (red)
|
|
|
|
|
|
let x_end_x = x + arrow_length * cos;
|
|
|
|
|
|
let x_end_y = y + arrow_length * sin;
|
|
|
|
|
|
let x_arrow = [
|
|
|
|
|
|
x, y,
|
|
|
|
|
|
x_end_x, x_end_y,
|
|
|
|
|
|
x_end_x - arrow_head * cos + arrow_head * 0.3 * sin,
|
|
|
|
|
|
x_end_y - arrow_head * sin - arrow_head * 0.3 * cos,
|
|
|
|
|
|
x_end_x, x_end_y,
|
|
|
|
|
|
x_end_x - arrow_head * cos - arrow_head * 0.3 * sin,
|
|
|
|
|
|
x_end_y - arrow_head * sin + arrow_head * 0.3 * cos,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
|
|
let array = js_sys::Float32Array::view(&x_arrow);
|
|
|
|
|
|
gl.buffer_data_with_array_buffer_view(
|
|
|
|
|
|
WebGl2RenderingContext::ARRAY_BUFFER,
|
|
|
|
|
|
&array,
|
|
|
|
|
|
WebGl2RenderingContext::DYNAMIC_DRAW,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
gl.uniform4f(color_loc.as_ref(), 1.0, 0.3, 0.3, 1.0);
|
|
|
|
|
|
gl.draw_arrays(WebGl2RenderingContext::LINE_STRIP, 0, 5);
|
|
|
|
|
|
|
|
|
|
|
|
// Y axis (green)
|
|
|
|
|
|
let y_end_x = x - arrow_length * sin;
|
|
|
|
|
|
let y_end_y = y + arrow_length * cos;
|
|
|
|
|
|
let y_arrow = [
|
|
|
|
|
|
x, y,
|
|
|
|
|
|
y_end_x, y_end_y,
|
|
|
|
|
|
y_end_x + arrow_head * sin + arrow_head * 0.3 * cos,
|
|
|
|
|
|
y_end_y - arrow_head * cos + arrow_head * 0.3 * sin,
|
|
|
|
|
|
y_end_x, y_end_y,
|
|
|
|
|
|
y_end_x + arrow_head * sin - arrow_head * 0.3 * cos,
|
|
|
|
|
|
y_end_y - arrow_head * cos - arrow_head * 0.3 * sin,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
|
|
let array = js_sys::Float32Array::view(&y_arrow);
|
|
|
|
|
|
gl.buffer_data_with_array_buffer_view(
|
|
|
|
|
|
WebGl2RenderingContext::ARRAY_BUFFER,
|
|
|
|
|
|
&array,
|
|
|
|
|
|
WebGl2RenderingContext::DYNAMIC_DRAW,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
gl.uniform4f(color_loc.as_ref(), 0.3, 1.0, 0.3, 1.0);
|
|
|
|
|
|
gl.draw_arrays(WebGl2RenderingContext::LINE_STRIP, 0, 5);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Draw rotation handle (circle).
|
|
|
|
|
|
/// 绘制旋转手柄(圆形)。
|
|
|
|
|
|
fn draw_rotate_handles(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
gl: &WebGl2RenderingContext,
|
|
|
|
|
|
color_loc: &Option<web_sys::WebGlUniformLocation>,
|
|
|
|
|
|
x: f32,
|
|
|
|
|
|
y: f32,
|
|
|
|
|
|
radius: f32,
|
|
|
|
|
|
_camera: &Camera2D,
|
|
|
|
|
|
) {
|
|
|
|
|
|
let segments = 32;
|
|
|
|
|
|
let mut vertices = Vec::with_capacity(segments * 2);
|
|
|
|
|
|
|
|
|
|
|
|
for i in 0..segments {
|
|
|
|
|
|
let angle = (i 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(), 0.3, 0.6, 1.0, 1.0);
|
|
|
|
|
|
gl.draw_arrays(WebGl2RenderingContext::LINE_LOOP, 0, segments as i32);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Draw scale handles (squares at corners).
|
|
|
|
|
|
/// 绘制缩放手柄(角落的方块)。
|
|
|
|
|
|
fn draw_scale_handles(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
gl: &WebGl2RenderingContext,
|
|
|
|
|
|
color_loc: &Option<web_sys::WebGlUniformLocation>,
|
|
|
|
|
|
x: f32,
|
|
|
|
|
|
y: f32,
|
|
|
|
|
|
width: f32,
|
|
|
|
|
|
height: f32,
|
|
|
|
|
|
rotation: f32,
|
|
|
|
|
|
origin_x: f32,
|
|
|
|
|
|
origin_y: f32,
|
|
|
|
|
|
camera: &Camera2D,
|
|
|
|
|
|
) {
|
|
|
|
|
|
let handle_size = 6.0 / camera.zoom;
|
|
|
|
|
|
let corners = self.calculate_rect_vertices(x, y, width, height, rotation, origin_x, origin_y);
|
|
|
|
|
|
|
|
|
|
|
|
// Draw a small square at each corner
|
|
|
|
|
|
for i in 0..4 {
|
|
|
|
|
|
let cx = corners[i * 2];
|
|
|
|
|
|
let cy = corners[i * 2 + 1];
|
|
|
|
|
|
|
|
|
|
|
|
let square = [
|
|
|
|
|
|
cx - handle_size, cy - handle_size,
|
|
|
|
|
|
cx + handle_size, cy - handle_size,
|
|
|
|
|
|
cx + handle_size, cy + handle_size,
|
|
|
|
|
|
cx - handle_size, cy + handle_size,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
|
|
let array = js_sys::Float32Array::view(&square);
|
|
|
|
|
|
gl.buffer_data_with_array_buffer_view(
|
|
|
|
|
|
WebGl2RenderingContext::ARRAY_BUFFER,
|
|
|
|
|
|
&array,
|
|
|
|
|
|
WebGl2RenderingContext::DYNAMIC_DRAW,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
gl.uniform4f(color_loc.as_ref(), 1.0, 0.8, 0.2, 1.0);
|
|
|
|
|
|
gl.draw_arrays(WebGl2RenderingContext::LINE_LOOP, 0, 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Calculate the 4 corner vertices of a rotated rectangle.
|
|
|
|
|
|
/// 计算旋转矩形的4个角点顶点。
|
|
|
|
|
|
fn calculate_rect_vertices(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
x: f32,
|
|
|
|
|
|
y: f32,
|
|
|
|
|
|
width: f32,
|
|
|
|
|
|
height: f32,
|
|
|
|
|
|
rotation: f32,
|
|
|
|
|
|
origin_x: f32,
|
|
|
|
|
|
origin_y: f32,
|
|
|
|
|
|
) -> [f32; 8] {
|
|
|
|
|
|
let cos = rotation.cos();
|
|
|
|
|
|
let sin = rotation.sin();
|
|
|
|
|
|
|
|
|
|
|
|
// Origin offset
|
|
|
|
|
|
let ox = origin_x * width;
|
|
|
|
|
|
let oy = origin_y * height;
|
|
|
|
|
|
|
|
|
|
|
|
// Local corner positions (relative to origin)
|
|
|
|
|
|
// Y-up coordinate system
|
|
|
|
|
|
let corners = [
|
|
|
|
|
|
(-ox, height - oy), // Top-left
|
|
|
|
|
|
(width - ox, height - oy), // Top-right
|
|
|
|
|
|
(width - ox, -oy), // Bottom-right
|
|
|
|
|
|
(-ox, -oy), // Bottom-left
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
let mut vertices = [0.0f32; 8];
|
|
|
|
|
|
for (i, (lx, ly)) in corners.iter().enumerate() {
|
|
|
|
|
|
// Apply rotation
|
|
|
|
|
|
let rx = lx * cos - ly * sin;
|
|
|
|
|
|
let ry = lx * sin + ly * cos;
|
|
|
|
|
|
|
|
|
|
|
|
// Apply translation
|
|
|
|
|
|
vertices[i * 2] = rx + x;
|
|
|
|
|
|
vertices[i * 2 + 1] = ry + y;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
vertices
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|