feat: 集成Rust WASM渲染引擎与TypeScript ECS框架 (#228)
* feat: 集成Rust WASM渲染引擎与TypeScript ECS框架 * feat: 增强编辑器UI功能与跨平台支持 * fix: 修复CI测试和类型检查问题 * fix: 修复CI问题并提高测试覆盖率 * fix: 修复CI问题并提高测试覆盖率
This commit is contained in:
8
packages/engine/src/renderer/batch/mod.rs
Normal file
8
packages/engine/src/renderer/batch/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
//! Sprite batch rendering system.
|
||||
//! 精灵批处理渲染系统。
|
||||
|
||||
mod sprite_batch;
|
||||
mod vertex;
|
||||
|
||||
pub use sprite_batch::SpriteBatch;
|
||||
pub use vertex::{SpriteVertex, VERTEX_SIZE};
|
||||
401
packages/engine/src/renderer/batch/sprite_batch.rs
Normal file
401
packages/engine/src/renderer/batch/sprite_batch.rs
Normal file
@@ -0,0 +1,401 @@
|
||||
//! Sprite batch renderer for efficient 2D rendering.
|
||||
//! 用于高效2D渲染的精灵批处理渲染器。
|
||||
|
||||
use web_sys::{
|
||||
WebGl2RenderingContext, WebGlBuffer, WebGlVertexArrayObject,
|
||||
};
|
||||
|
||||
use crate::core::error::{EngineError, Result};
|
||||
use crate::math::Color;
|
||||
use crate::resource::TextureManager;
|
||||
use super::vertex::FLOATS_PER_VERTEX;
|
||||
|
||||
/// Number of vertices per sprite (quad).
|
||||
/// 每个精灵的顶点数(四边形)。
|
||||
const VERTICES_PER_SPRITE: usize = 4;
|
||||
|
||||
/// Number of indices per sprite (2 triangles).
|
||||
/// 每个精灵的索引数(2个三角形)。
|
||||
const INDICES_PER_SPRITE: usize = 6;
|
||||
|
||||
/// Transform data stride (x, y, rotation, scaleX, scaleY, originX, originY).
|
||||
/// 变换数据步长。
|
||||
const TRANSFORM_STRIDE: usize = 7;
|
||||
|
||||
/// UV data stride (u0, v0, u1, v1).
|
||||
/// UV数据步长。
|
||||
const UV_STRIDE: usize = 4;
|
||||
|
||||
/// Sprite batch renderer.
|
||||
/// 精灵批处理渲染器。
|
||||
///
|
||||
/// Batches multiple sprites into a single draw call for optimal performance.
|
||||
/// 将多个精灵合并为单次绘制调用以获得最佳性能。
|
||||
///
|
||||
/// # Performance | 性能
|
||||
/// - Uses dynamic vertex buffer for efficient updates | 使用动态顶点缓冲区以高效更新
|
||||
/// - Minimizes state changes and draw calls | 最小化状态更改和绘制调用
|
||||
/// - Supports up to 10000+ sprites per batch | 每批次支持10000+精灵
|
||||
pub struct SpriteBatch {
|
||||
/// Vertex array object.
|
||||
/// 顶点数组对象。
|
||||
vao: WebGlVertexArrayObject,
|
||||
|
||||
/// Vertex buffer object.
|
||||
/// 顶点缓冲区对象。
|
||||
vbo: WebGlBuffer,
|
||||
|
||||
/// Index buffer object.
|
||||
/// 索引缓冲区对象。
|
||||
ibo: WebGlBuffer,
|
||||
|
||||
/// Maximum number of sprites.
|
||||
/// 最大精灵数。
|
||||
max_sprites: usize,
|
||||
|
||||
/// Vertex data buffer.
|
||||
/// 顶点数据缓冲区。
|
||||
vertices: Vec<f32>,
|
||||
|
||||
/// Current number of sprites in batch.
|
||||
/// 当前批次中的精灵数。
|
||||
sprite_count: usize,
|
||||
|
||||
/// Current texture ID being batched.
|
||||
/// 当前正在批处理的纹理ID。
|
||||
current_texture: Option<u32>,
|
||||
}
|
||||
|
||||
impl SpriteBatch {
|
||||
/// Create a new sprite batch.
|
||||
/// 创建新的精灵批处理器。
|
||||
///
|
||||
/// # Arguments | 参数
|
||||
/// * `gl` - WebGL2 context | WebGL2上下文
|
||||
/// * `max_sprites` - Maximum sprites per batch | 每批次最大精灵数
|
||||
pub fn new(gl: &WebGl2RenderingContext, max_sprites: usize) -> Result<Self> {
|
||||
// Create VAO | 创建VAO
|
||||
let vao = gl
|
||||
.create_vertex_array()
|
||||
.ok_or(EngineError::BufferCreationFailed)?;
|
||||
gl.bind_vertex_array(Some(&vao));
|
||||
|
||||
// Create vertex buffer | 创建顶点缓冲区
|
||||
let vbo = gl
|
||||
.create_buffer()
|
||||
.ok_or(EngineError::BufferCreationFailed)?;
|
||||
gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&vbo));
|
||||
|
||||
// Allocate vertex buffer memory | 分配顶点缓冲区内存
|
||||
let vertex_buffer_size = max_sprites * VERTICES_PER_SPRITE * FLOATS_PER_VERTEX * 4;
|
||||
gl.buffer_data_with_i32(
|
||||
WebGl2RenderingContext::ARRAY_BUFFER,
|
||||
vertex_buffer_size as i32,
|
||||
WebGl2RenderingContext::DYNAMIC_DRAW,
|
||||
);
|
||||
|
||||
// Create and populate index buffer | 创建并填充索引缓冲区
|
||||
let ibo = gl
|
||||
.create_buffer()
|
||||
.ok_or(EngineError::BufferCreationFailed)?;
|
||||
gl.bind_buffer(WebGl2RenderingContext::ELEMENT_ARRAY_BUFFER, Some(&ibo));
|
||||
|
||||
let indices = Self::generate_indices(max_sprites);
|
||||
unsafe {
|
||||
let index_array = js_sys::Uint16Array::view(&indices);
|
||||
gl.buffer_data_with_array_buffer_view(
|
||||
WebGl2RenderingContext::ELEMENT_ARRAY_BUFFER,
|
||||
&index_array,
|
||||
WebGl2RenderingContext::STATIC_DRAW,
|
||||
);
|
||||
}
|
||||
|
||||
// Set up vertex attributes | 设置顶点属性
|
||||
Self::setup_vertex_attributes(gl);
|
||||
|
||||
// Unbind VAO | 解绑VAO
|
||||
gl.bind_vertex_array(None);
|
||||
|
||||
log::debug!(
|
||||
"SpriteBatch created with capacity: {} sprites | SpriteBatch创建完成,容量: {}个精灵",
|
||||
max_sprites,
|
||||
max_sprites
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
vao,
|
||||
vbo,
|
||||
ibo,
|
||||
max_sprites,
|
||||
vertices: Vec::with_capacity(max_sprites * VERTICES_PER_SPRITE * FLOATS_PER_VERTEX),
|
||||
sprite_count: 0,
|
||||
current_texture: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate index buffer data.
|
||||
/// 生成索引缓冲区数据。
|
||||
fn generate_indices(max_sprites: usize) -> Vec<u16> {
|
||||
let mut indices = Vec::with_capacity(max_sprites * INDICES_PER_SPRITE);
|
||||
|
||||
for i in 0..max_sprites {
|
||||
let base = (i * VERTICES_PER_SPRITE) as u16;
|
||||
// Two triangles per sprite | 每个精灵两个三角形
|
||||
// Triangle 1: 0, 1, 2 | 三角形1
|
||||
// Triangle 2: 2, 3, 0 | 三角形2
|
||||
indices.push(base);
|
||||
indices.push(base + 1);
|
||||
indices.push(base + 2);
|
||||
indices.push(base + 2);
|
||||
indices.push(base + 3);
|
||||
indices.push(base);
|
||||
}
|
||||
|
||||
indices
|
||||
}
|
||||
|
||||
/// Set up vertex attribute pointers.
|
||||
/// 设置顶点属性指针。
|
||||
fn setup_vertex_attributes(gl: &WebGl2RenderingContext) {
|
||||
let stride = (FLOATS_PER_VERTEX * 4) as i32;
|
||||
|
||||
// Position attribute (location = 0) | 位置属性
|
||||
gl.enable_vertex_attrib_array(0);
|
||||
gl.vertex_attrib_pointer_with_i32(
|
||||
0,
|
||||
2,
|
||||
WebGl2RenderingContext::FLOAT,
|
||||
false,
|
||||
stride,
|
||||
0,
|
||||
);
|
||||
|
||||
// Texture coordinate attribute (location = 1) | 纹理坐标属性
|
||||
gl.enable_vertex_attrib_array(1);
|
||||
gl.vertex_attrib_pointer_with_i32(
|
||||
1,
|
||||
2,
|
||||
WebGl2RenderingContext::FLOAT,
|
||||
false,
|
||||
stride,
|
||||
8, // 2 floats * 4 bytes
|
||||
);
|
||||
|
||||
// Color attribute (location = 2) | 颜色属性
|
||||
gl.enable_vertex_attrib_array(2);
|
||||
gl.vertex_attrib_pointer_with_i32(
|
||||
2,
|
||||
4,
|
||||
WebGl2RenderingContext::FLOAT,
|
||||
false,
|
||||
stride,
|
||||
16, // 4 floats * 4 bytes
|
||||
);
|
||||
}
|
||||
|
||||
/// Clear the batch for a new frame.
|
||||
/// 为新帧清空批处理。
|
||||
pub fn clear(&mut self) {
|
||||
self.vertices.clear();
|
||||
self.sprite_count = 0;
|
||||
self.current_texture = None;
|
||||
}
|
||||
|
||||
/// Add sprites from batch data.
|
||||
/// 从批处理数据添加精灵。
|
||||
///
|
||||
/// # Arguments | 参数
|
||||
/// * `transforms` - [x, y, rotation, scaleX, scaleY, originX, originY] per sprite
|
||||
/// * `texture_ids` - Texture ID for each sprite | 每个精灵的纹理ID
|
||||
/// * `uvs` - [u0, v0, u1, v1] per sprite | 每个精灵的UV坐标
|
||||
/// * `colors` - Packed RGBA color per sprite | 每个精灵的打包RGBA颜色
|
||||
/// * `texture_manager` - Texture manager for getting texture sizes | 纹理管理器
|
||||
pub fn add_sprites(
|
||||
&mut self,
|
||||
transforms: &[f32],
|
||||
texture_ids: &[u32],
|
||||
uvs: &[f32],
|
||||
colors: &[u32],
|
||||
texture_manager: &TextureManager,
|
||||
) -> Result<()> {
|
||||
let sprite_count = texture_ids.len();
|
||||
|
||||
// Validate input data | 验证输入数据
|
||||
if transforms.len() != sprite_count * TRANSFORM_STRIDE {
|
||||
return Err(EngineError::InvalidBatchData(format!(
|
||||
"Transform data length mismatch: expected {}, got {}",
|
||||
sprite_count * TRANSFORM_STRIDE,
|
||||
transforms.len()
|
||||
)));
|
||||
}
|
||||
|
||||
if uvs.len() != sprite_count * UV_STRIDE {
|
||||
return Err(EngineError::InvalidBatchData(format!(
|
||||
"UV data length mismatch: expected {}, got {}",
|
||||
sprite_count * UV_STRIDE,
|
||||
uvs.len()
|
||||
)));
|
||||
}
|
||||
|
||||
if colors.len() != sprite_count {
|
||||
return Err(EngineError::InvalidBatchData(format!(
|
||||
"Color data length mismatch: expected {}, got {}",
|
||||
sprite_count,
|
||||
colors.len()
|
||||
)));
|
||||
}
|
||||
|
||||
// Check capacity | 检查容量
|
||||
if self.sprite_count + sprite_count > self.max_sprites {
|
||||
return Err(EngineError::InvalidBatchData(format!(
|
||||
"Batch capacity exceeded: {} + {} > {}",
|
||||
self.sprite_count, sprite_count, self.max_sprites
|
||||
)));
|
||||
}
|
||||
|
||||
// Add each sprite | 添加每个精灵
|
||||
for i in 0..sprite_count {
|
||||
let t_offset = i * TRANSFORM_STRIDE;
|
||||
let uv_offset = i * UV_STRIDE;
|
||||
|
||||
let x = transforms[t_offset];
|
||||
let y = transforms[t_offset + 1];
|
||||
let rotation = transforms[t_offset + 2];
|
||||
let scale_x = transforms[t_offset + 3];
|
||||
let scale_y = transforms[t_offset + 4];
|
||||
let origin_x = transforms[t_offset + 5];
|
||||
let origin_y = transforms[t_offset + 6];
|
||||
|
||||
let u0 = uvs[uv_offset];
|
||||
let v0 = uvs[uv_offset + 1];
|
||||
let u1 = uvs[uv_offset + 2];
|
||||
let v1 = uvs[uv_offset + 3];
|
||||
|
||||
let color = Color::from_packed(colors[i]);
|
||||
let color_arr = [color.r, color.g, color.b, color.a];
|
||||
|
||||
// Get texture size for this sprite | 获取此精灵的纹理尺寸
|
||||
let (tex_width, tex_height) = texture_manager
|
||||
.get_texture_size(texture_ids[i])
|
||||
.unwrap_or((64.0, 64.0));
|
||||
|
||||
let width = tex_width * scale_x;
|
||||
let height = tex_height * scale_y;
|
||||
|
||||
// Calculate transformed vertices | 计算变换后的顶点
|
||||
self.add_sprite_vertices(
|
||||
x, y, width, height, rotation, origin_x, origin_y,
|
||||
u0, v0, u1, v1, color_arr,
|
||||
);
|
||||
}
|
||||
|
||||
self.sprite_count += sprite_count;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add vertices for a single sprite.
|
||||
/// 为单个精灵添加顶点。
|
||||
#[inline]
|
||||
fn add_sprite_vertices(
|
||||
&mut self,
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
rotation: f32,
|
||||
origin_x: f32,
|
||||
origin_y: f32,
|
||||
u0: f32,
|
||||
v0: f32,
|
||||
u1: f32,
|
||||
v1: f32,
|
||||
color: [f32; 4],
|
||||
) {
|
||||
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) | 局部角点位置(相对于原点)
|
||||
let corners = [
|
||||
(-ox, -oy), // Top-left | 左上
|
||||
(width - ox, -oy), // Top-right | 右上
|
||||
(width - ox, height - oy), // Bottom-right | 右下
|
||||
(-ox, height - oy), // Bottom-left | 左下
|
||||
];
|
||||
|
||||
let tex_coords = [
|
||||
[u0, v0], // Top-left
|
||||
[u1, v0], // Top-right
|
||||
[u1, v1], // Bottom-right
|
||||
[u0, v1], // Bottom-left
|
||||
];
|
||||
|
||||
// Transform and add each vertex | 变换并添加每个顶点
|
||||
for i in 0..4 {
|
||||
let (lx, ly) = corners[i];
|
||||
|
||||
// Apply rotation | 应用旋转
|
||||
let rx = lx * cos - ly * sin;
|
||||
let ry = lx * sin + ly * cos;
|
||||
|
||||
// Apply translation | 应用平移
|
||||
let px = rx + x;
|
||||
let py = ry + y;
|
||||
|
||||
// Position | 位置
|
||||
self.vertices.push(px);
|
||||
self.vertices.push(py);
|
||||
|
||||
// Texture coordinates | 纹理坐标
|
||||
self.vertices.push(tex_coords[i][0]);
|
||||
self.vertices.push(tex_coords[i][1]);
|
||||
|
||||
// Color | 颜色
|
||||
self.vertices.extend_from_slice(&color);
|
||||
}
|
||||
}
|
||||
|
||||
/// Flush the batch to GPU and render.
|
||||
/// 将批处理刷新到GPU并渲染。
|
||||
pub fn flush(&mut self, gl: &WebGl2RenderingContext) {
|
||||
if self.sprite_count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind VAO | 绑定VAO
|
||||
gl.bind_vertex_array(Some(&self.vao));
|
||||
|
||||
// Upload vertex data | 上传顶点数据
|
||||
gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&self.vbo));
|
||||
unsafe {
|
||||
let vertex_array = js_sys::Float32Array::view(&self.vertices);
|
||||
gl.buffer_sub_data_with_i32_and_array_buffer_view(
|
||||
WebGl2RenderingContext::ARRAY_BUFFER,
|
||||
0,
|
||||
&vertex_array,
|
||||
);
|
||||
}
|
||||
|
||||
// Draw | 绘制
|
||||
let index_count = (self.sprite_count * INDICES_PER_SPRITE) as i32;
|
||||
gl.draw_elements_with_i32(
|
||||
WebGl2RenderingContext::TRIANGLES,
|
||||
index_count,
|
||||
WebGl2RenderingContext::UNSIGNED_SHORT,
|
||||
0,
|
||||
);
|
||||
|
||||
// Unbind VAO | 解绑VAO
|
||||
gl.bind_vertex_array(None);
|
||||
}
|
||||
|
||||
/// Get current sprite count.
|
||||
/// 获取当前精灵数量。
|
||||
#[inline]
|
||||
pub fn sprite_count(&self) -> usize {
|
||||
self.sprite_count
|
||||
}
|
||||
}
|
||||
60
packages/engine/src/renderer/batch/vertex.rs
Normal file
60
packages/engine/src/renderer/batch/vertex.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
//! Vertex data structures for sprite rendering.
|
||||
//! 用于精灵渲染的顶点数据结构。
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
/// Size of a single sprite vertex in bytes.
|
||||
/// 单个精灵顶点的字节大小。
|
||||
pub const VERTEX_SIZE: usize = std::mem::size_of::<SpriteVertex>();
|
||||
|
||||
/// Number of floats per vertex.
|
||||
/// 每个顶点的浮点数数量。
|
||||
pub const FLOATS_PER_VERTEX: usize = 8;
|
||||
|
||||
/// Sprite vertex data.
|
||||
/// 精灵顶点数据。
|
||||
///
|
||||
/// Each sprite requires 4 vertices (quad), each with position, UV, and color.
|
||||
/// 每个精灵需要4个顶点(四边形),每个顶点包含位置、UV和颜色。
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct SpriteVertex {
|
||||
/// Position (x, y).
|
||||
/// 位置。
|
||||
pub position: [f32; 2],
|
||||
|
||||
/// Texture coordinates (u, v).
|
||||
/// 纹理坐标。
|
||||
pub tex_coord: [f32; 2],
|
||||
|
||||
/// Color (r, g, b, a).
|
||||
/// 颜色。
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
|
||||
impl SpriteVertex {
|
||||
/// Create a new sprite vertex.
|
||||
/// 创建新的精灵顶点。
|
||||
#[inline]
|
||||
pub const fn new(
|
||||
position: [f32; 2],
|
||||
tex_coord: [f32; 2],
|
||||
color: [f32; 4],
|
||||
) -> Self {
|
||||
Self {
|
||||
position,
|
||||
tex_coord,
|
||||
color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SpriteVertex {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
position: [0.0, 0.0],
|
||||
tex_coord: [0.0, 0.0],
|
||||
color: [1.0, 1.0, 1.0, 1.0],
|
||||
}
|
||||
}
|
||||
}
|
||||
144
packages/engine/src/renderer/camera.rs
Normal file
144
packages/engine/src/renderer/camera.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
//! 2D camera implementation.
|
||||
//! 2D相机实现。
|
||||
|
||||
use crate::math::Vec2;
|
||||
use glam::Mat3;
|
||||
|
||||
/// 2D orthographic camera.
|
||||
/// 2D正交相机。
|
||||
///
|
||||
/// Provides view and projection matrices for 2D rendering.
|
||||
/// 提供用于2D渲染的视图和投影矩阵。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Camera2D {
|
||||
/// Camera position in world space.
|
||||
/// 相机在世界空间中的位置。
|
||||
pub position: Vec2,
|
||||
|
||||
/// Rotation in radians.
|
||||
/// 旋转角度(弧度)。
|
||||
pub rotation: f32,
|
||||
|
||||
/// Zoom level (1.0 = normal).
|
||||
/// 缩放级别(1.0 = 正常)。
|
||||
pub zoom: f32,
|
||||
|
||||
/// Viewport width.
|
||||
/// 视口宽度。
|
||||
width: f32,
|
||||
|
||||
/// Viewport height.
|
||||
/// 视口高度。
|
||||
height: f32,
|
||||
}
|
||||
|
||||
impl Camera2D {
|
||||
/// Create a new 2D camera.
|
||||
/// 创建新的2D相机。
|
||||
///
|
||||
/// # Arguments | 参数
|
||||
/// * `width` - Viewport width | 视口宽度
|
||||
/// * `height` - Viewport height | 视口高度
|
||||
pub fn new(width: f32, height: f32) -> Self {
|
||||
Self {
|
||||
position: Vec2::ZERO,
|
||||
rotation: 0.0,
|
||||
zoom: 1.0,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update viewport size.
|
||||
/// 更新视口大小。
|
||||
pub fn set_viewport(&mut self, width: f32, height: f32) {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
}
|
||||
|
||||
/// Get the projection matrix.
|
||||
/// 获取投影矩阵。
|
||||
///
|
||||
/// Creates an orthographic projection that maps screen coordinates
|
||||
/// to normalized device coordinates.
|
||||
/// 创建将屏幕坐标映射到标准化设备坐标的正交投影。
|
||||
pub fn projection_matrix(&self) -> Mat3 {
|
||||
// Orthographic projection | 正交投影
|
||||
// Maps [0, width] x [0, height] to [-1, 1] x [-1, 1]
|
||||
let sx = 2.0 / self.width * self.zoom;
|
||||
let sy = -2.0 / self.height * self.zoom; // Flip Y axis | 翻转Y轴
|
||||
|
||||
let cos = self.rotation.cos();
|
||||
let sin = self.rotation.sin();
|
||||
|
||||
// Apply zoom, rotation, and translation
|
||||
// 应用缩放、旋转和平移
|
||||
let tx = -self.position.x * sx * cos - self.position.y * sy * sin - 1.0;
|
||||
let ty = -self.position.x * sx * sin + self.position.y * sy * cos + 1.0;
|
||||
|
||||
Mat3::from_cols(
|
||||
glam::Vec3::new(sx * cos, sx * sin, 0.0),
|
||||
glam::Vec3::new(sy * -sin, sy * cos, 0.0),
|
||||
glam::Vec3::new(tx, ty, 1.0),
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert screen coordinates to world coordinates.
|
||||
/// 将屏幕坐标转换为世界坐标。
|
||||
pub fn screen_to_world(&self, screen: Vec2) -> Vec2 {
|
||||
let x = (screen.x / self.zoom) + self.position.x;
|
||||
let y = (screen.y / self.zoom) + self.position.y;
|
||||
|
||||
if self.rotation != 0.0 {
|
||||
let dx = x - self.position.x;
|
||||
let dy = y - self.position.y;
|
||||
let cos = (-self.rotation).cos();
|
||||
let sin = (-self.rotation).sin();
|
||||
|
||||
Vec2::new(
|
||||
dx * cos - dy * sin + self.position.x,
|
||||
dx * sin + dy * cos + self.position.y,
|
||||
)
|
||||
} else {
|
||||
Vec2::new(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert world coordinates to screen coordinates.
|
||||
/// 将世界坐标转换为屏幕坐标。
|
||||
pub fn world_to_screen(&self, world: Vec2) -> Vec2 {
|
||||
let dx = world.x - self.position.x;
|
||||
let dy = world.y - self.position.y;
|
||||
|
||||
if self.rotation != 0.0 {
|
||||
let cos = self.rotation.cos();
|
||||
let sin = self.rotation.sin();
|
||||
let rx = dx * cos - dy * sin;
|
||||
let ry = dx * sin + dy * cos;
|
||||
|
||||
Vec2::new(rx * self.zoom, ry * self.zoom)
|
||||
} else {
|
||||
Vec2::new(dx * self.zoom, dy * self.zoom)
|
||||
}
|
||||
}
|
||||
|
||||
/// Move camera by delta.
|
||||
/// 按增量移动相机。
|
||||
#[inline]
|
||||
pub fn translate(&mut self, delta: Vec2) {
|
||||
self.position = self.position + delta;
|
||||
}
|
||||
|
||||
/// Set zoom level with clamping.
|
||||
/// 设置缩放级别并限制范围。
|
||||
#[inline]
|
||||
pub fn set_zoom(&mut self, zoom: f32) {
|
||||
self.zoom = zoom.clamp(0.1, 10.0);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Camera2D {
|
||||
fn default() -> Self {
|
||||
Self::new(800.0, 600.0)
|
||||
}
|
||||
}
|
||||
14
packages/engine/src/renderer/mod.rs
Normal file
14
packages/engine/src/renderer/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
//! 2D rendering system with batch optimization.
|
||||
//! 带批处理优化的2D渲染系统。
|
||||
|
||||
pub mod batch;
|
||||
pub mod shader;
|
||||
pub mod texture;
|
||||
|
||||
mod renderer2d;
|
||||
mod camera;
|
||||
|
||||
pub use renderer2d::Renderer2D;
|
||||
pub use camera::Camera2D;
|
||||
pub use batch::SpriteBatch;
|
||||
pub use texture::{Texture, TextureManager};
|
||||
134
packages/engine/src/renderer/renderer2d.rs
Normal file
134
packages/engine/src/renderer/renderer2d.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
//! Main 2D renderer implementation.
|
||||
//! 主2D渲染器实现。
|
||||
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::WebGl2RenderingContext;
|
||||
|
||||
use crate::core::error::Result;
|
||||
use crate::resource::TextureManager;
|
||||
use super::batch::SpriteBatch;
|
||||
use super::camera::Camera2D;
|
||||
use super::shader::{ShaderProgram, SPRITE_VERTEX_SHADER, SPRITE_FRAGMENT_SHADER};
|
||||
|
||||
/// 2D renderer with batched sprite rendering.
|
||||
/// 带批处理精灵渲染的2D渲染器。
|
||||
///
|
||||
/// Coordinates sprite batching, shader management, and camera transforms.
|
||||
/// 协调精灵批处理、Shader管理和相机变换。
|
||||
pub struct Renderer2D {
|
||||
/// Sprite batch renderer.
|
||||
/// 精灵批处理渲染器。
|
||||
sprite_batch: SpriteBatch,
|
||||
|
||||
/// Sprite shader program.
|
||||
/// 精灵Shader程序。
|
||||
shader: ShaderProgram,
|
||||
|
||||
/// 2D camera.
|
||||
/// 2D相机。
|
||||
camera: Camera2D,
|
||||
}
|
||||
|
||||
impl Renderer2D {
|
||||
/// Create a new 2D renderer.
|
||||
/// 创建新的2D渲染器。
|
||||
///
|
||||
/// # Arguments | 参数
|
||||
/// * `gl` - WebGL2 context | WebGL2上下文
|
||||
/// * `max_sprites` - Maximum sprites per batch | 每批次最大精灵数
|
||||
pub fn new(gl: &WebGl2RenderingContext, max_sprites: usize) -> Result<Self> {
|
||||
let sprite_batch = SpriteBatch::new(gl, max_sprites)?;
|
||||
let shader = ShaderProgram::new(gl, SPRITE_VERTEX_SHADER, SPRITE_FRAGMENT_SHADER)?;
|
||||
|
||||
// Get canvas size for camera | 获取canvas尺寸用于相机
|
||||
let canvas = gl.canvas()
|
||||
.and_then(|c| c.dyn_into::<web_sys::HtmlCanvasElement>().ok())
|
||||
.map(|c| (c.width() as f32, c.height() as f32))
|
||||
.unwrap_or((800.0, 600.0));
|
||||
|
||||
let camera = Camera2D::new(canvas.0, canvas.1);
|
||||
|
||||
log::info!(
|
||||
"Renderer2D initialized | Renderer2D初始化完成: {}x{}, max sprites: {}",
|
||||
canvas.0, canvas.1, max_sprites
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
sprite_batch,
|
||||
shader,
|
||||
camera,
|
||||
})
|
||||
}
|
||||
|
||||
/// Submit sprite batch data for rendering.
|
||||
/// 提交精灵批次数据进行渲染。
|
||||
///
|
||||
/// # Arguments | 参数
|
||||
/// * `transforms` - Transform data for each sprite | 每个精灵的变换数据
|
||||
/// * `texture_ids` - Texture ID for each sprite | 每个精灵的纹理ID
|
||||
/// * `uvs` - UV coordinates for each sprite | 每个精灵的UV坐标
|
||||
/// * `colors` - Packed color for each sprite | 每个精灵的打包颜色
|
||||
/// * `texture_manager` - Texture manager | 纹理管理器
|
||||
pub fn submit_batch(
|
||||
&mut self,
|
||||
transforms: &[f32],
|
||||
texture_ids: &[u32],
|
||||
uvs: &[f32],
|
||||
colors: &[u32],
|
||||
texture_manager: &TextureManager,
|
||||
) -> Result<()> {
|
||||
self.sprite_batch.add_sprites(
|
||||
transforms,
|
||||
texture_ids,
|
||||
uvs,
|
||||
colors,
|
||||
texture_manager,
|
||||
)
|
||||
}
|
||||
|
||||
/// Render the current frame.
|
||||
/// 渲染当前帧。
|
||||
pub fn render(&mut self, gl: &WebGl2RenderingContext) -> Result<()> {
|
||||
if self.sprite_batch.sprite_count() == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Bind shader | 绑定Shader
|
||||
self.shader.bind(gl);
|
||||
|
||||
// Set projection matrix | 设置投影矩阵
|
||||
let projection = self.camera.projection_matrix();
|
||||
self.shader.set_uniform_mat3(gl, "u_projection", &projection.to_cols_array());
|
||||
|
||||
// Set texture sampler | 设置纹理采样器
|
||||
self.shader.set_uniform_i32(gl, "u_texture", 0);
|
||||
|
||||
// Flush sprite batch | 刷新精灵批处理
|
||||
self.sprite_batch.flush(gl);
|
||||
|
||||
// Clear batch for next frame | 清空批处理以供下一帧使用
|
||||
self.sprite_batch.clear();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get mutable reference to camera.
|
||||
/// 获取相机的可变引用。
|
||||
#[inline]
|
||||
pub fn camera_mut(&mut self) -> &mut Camera2D {
|
||||
&mut self.camera
|
||||
}
|
||||
|
||||
/// Get reference to camera.
|
||||
/// 获取相机的引用。
|
||||
#[inline]
|
||||
pub fn camera(&self) -> &Camera2D {
|
||||
&self.camera
|
||||
}
|
||||
|
||||
/// Update camera viewport size.
|
||||
/// 更新相机视口大小。
|
||||
pub fn resize(&mut self, width: f32, height: f32) {
|
||||
self.camera.set_viewport(width, height);
|
||||
}
|
||||
}
|
||||
63
packages/engine/src/renderer/shader/builtin.rs
Normal file
63
packages/engine/src/renderer/shader/builtin.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
//! Built-in shader source code.
|
||||
//! 内置Shader源代码。
|
||||
|
||||
/// Sprite vertex shader source.
|
||||
/// 精灵顶点着色器源代码。
|
||||
///
|
||||
/// Handles sprite transformation with position, UV, and color attributes.
|
||||
/// 处理带有位置、UV和颜色属性的精灵变换。
|
||||
pub const SPRITE_VERTEX_SHADER: &str = r#"#version 300 es
|
||||
precision highp float;
|
||||
|
||||
// Vertex attributes | 顶点属性
|
||||
layout(location = 0) in vec2 a_position;
|
||||
layout(location = 1) in vec2 a_texCoord;
|
||||
layout(location = 2) in vec4 a_color;
|
||||
|
||||
// Uniforms | 统一变量
|
||||
uniform mat3 u_projection;
|
||||
|
||||
// Outputs to fragment shader | 输出到片段着色器
|
||||
out vec2 v_texCoord;
|
||||
out vec4 v_color;
|
||||
|
||||
void main() {
|
||||
// Apply projection matrix | 应用投影矩阵
|
||||
vec3 pos = u_projection * vec3(a_position, 1.0);
|
||||
gl_Position = vec4(pos.xy, 0.0, 1.0);
|
||||
|
||||
// Pass through to fragment shader | 传递到片段着色器
|
||||
v_texCoord = a_texCoord;
|
||||
v_color = a_color;
|
||||
}
|
||||
"#;
|
||||
|
||||
/// Sprite fragment shader source.
|
||||
/// 精灵片段着色器源代码。
|
||||
///
|
||||
/// Samples texture and applies vertex color tinting.
|
||||
/// 采样纹理并应用顶点颜色着色。
|
||||
pub const SPRITE_FRAGMENT_SHADER: &str = r#"#version 300 es
|
||||
precision highp float;
|
||||
|
||||
// Inputs from vertex shader | 来自顶点着色器的输入
|
||||
in vec2 v_texCoord;
|
||||
in vec4 v_color;
|
||||
|
||||
// Texture sampler | 纹理采样器
|
||||
uniform sampler2D u_texture;
|
||||
|
||||
// Output color | 输出颜色
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
// Sample texture and multiply by vertex color | 采样纹理并乘以顶点颜色
|
||||
vec4 texColor = texture(u_texture, v_texCoord);
|
||||
fragColor = texColor * v_color;
|
||||
|
||||
// Discard fully transparent pixels | 丢弃完全透明的像素
|
||||
if (fragColor.a < 0.01) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
8
packages/engine/src/renderer/shader/mod.rs
Normal file
8
packages/engine/src/renderer/shader/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
//! Shader management system.
|
||||
//! Shader管理系统。
|
||||
|
||||
mod program;
|
||||
mod builtin;
|
||||
|
||||
pub use program::ShaderProgram;
|
||||
pub use builtin::{SPRITE_VERTEX_SHADER, SPRITE_FRAGMENT_SHADER};
|
||||
154
packages/engine/src/renderer/shader/program.rs
Normal file
154
packages/engine/src/renderer/shader/program.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
//! Shader program compilation and management.
|
||||
//! Shader程序编译和管理。
|
||||
|
||||
use web_sys::{WebGl2RenderingContext, WebGlProgram, WebGlShader, WebGlUniformLocation};
|
||||
use crate::core::error::{EngineError, Result};
|
||||
|
||||
/// Compiled shader program.
|
||||
/// 已编译的Shader程序。
|
||||
///
|
||||
/// Manages vertex and fragment shaders, including compilation and linking.
|
||||
/// 管理顶点和片段着色器,包括编译和链接。
|
||||
pub struct ShaderProgram {
|
||||
program: WebGlProgram,
|
||||
}
|
||||
|
||||
impl ShaderProgram {
|
||||
/// Create and compile a new shader program.
|
||||
/// 创建并编译新的Shader程序。
|
||||
///
|
||||
/// # Arguments | 参数
|
||||
/// * `gl` - WebGL2 context | WebGL2上下文
|
||||
/// * `vertex_source` - Vertex shader source code | 顶点着色器源代码
|
||||
/// * `fragment_source` - Fragment shader source code | 片段着色器源代码
|
||||
///
|
||||
/// # Returns | 返回
|
||||
/// A compiled shader program or an error | 已编译的Shader程序或错误
|
||||
pub fn new(
|
||||
gl: &WebGl2RenderingContext,
|
||||
vertex_source: &str,
|
||||
fragment_source: &str,
|
||||
) -> Result<Self> {
|
||||
// Compile shaders | 编译着色器
|
||||
let vertex_shader = Self::compile_shader(
|
||||
gl,
|
||||
WebGl2RenderingContext::VERTEX_SHADER,
|
||||
vertex_source,
|
||||
)?;
|
||||
|
||||
let fragment_shader = Self::compile_shader(
|
||||
gl,
|
||||
WebGl2RenderingContext::FRAGMENT_SHADER,
|
||||
fragment_source,
|
||||
)?;
|
||||
|
||||
// Create and link program | 创建并链接程序
|
||||
let program = gl
|
||||
.create_program()
|
||||
.ok_or_else(|| EngineError::ProgramLinkFailed("Failed to create program".into()))?;
|
||||
|
||||
gl.attach_shader(&program, &vertex_shader);
|
||||
gl.attach_shader(&program, &fragment_shader);
|
||||
gl.link_program(&program);
|
||||
|
||||
// Check for linking errors | 检查链接错误
|
||||
let success = gl
|
||||
.get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS)
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
|
||||
if !success {
|
||||
let log = gl.get_program_info_log(&program).unwrap_or_default();
|
||||
return Err(EngineError::ProgramLinkFailed(log));
|
||||
}
|
||||
|
||||
// Clean up shaders (they're linked to the program now)
|
||||
// 清理着色器(它们现在已链接到程序)
|
||||
gl.delete_shader(Some(&vertex_shader));
|
||||
gl.delete_shader(Some(&fragment_shader));
|
||||
|
||||
log::debug!("Shader program compiled successfully | Shader程序编译成功");
|
||||
|
||||
Ok(Self { program })
|
||||
}
|
||||
|
||||
/// Compile a single shader.
|
||||
/// 编译单个着色器。
|
||||
fn compile_shader(
|
||||
gl: &WebGl2RenderingContext,
|
||||
shader_type: u32,
|
||||
source: &str,
|
||||
) -> Result<WebGlShader> {
|
||||
let shader = gl
|
||||
.create_shader(shader_type)
|
||||
.ok_or_else(|| EngineError::ShaderCompileFailed("Failed to create shader".into()))?;
|
||||
|
||||
gl.shader_source(&shader, source);
|
||||
gl.compile_shader(&shader);
|
||||
|
||||
// Check for compilation errors | 检查编译错误
|
||||
let success = gl
|
||||
.get_shader_parameter(&shader, WebGl2RenderingContext::COMPILE_STATUS)
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
|
||||
if !success {
|
||||
let log = gl.get_shader_info_log(&shader).unwrap_or_default();
|
||||
let shader_type_name = if shader_type == WebGl2RenderingContext::VERTEX_SHADER {
|
||||
"Vertex"
|
||||
} else {
|
||||
"Fragment"
|
||||
};
|
||||
return Err(EngineError::ShaderCompileFailed(format!(
|
||||
"{} shader: {}",
|
||||
shader_type_name, log
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(shader)
|
||||
}
|
||||
|
||||
/// Use this shader program for rendering.
|
||||
/// 使用此Shader程序进行渲染。
|
||||
#[inline]
|
||||
pub fn bind(&self, gl: &WebGl2RenderingContext) {
|
||||
gl.use_program(Some(&self.program));
|
||||
}
|
||||
|
||||
/// Get uniform location by name.
|
||||
/// 按名称获取uniform位置。
|
||||
#[inline]
|
||||
pub fn get_uniform_location(
|
||||
&self,
|
||||
gl: &WebGl2RenderingContext,
|
||||
name: &str,
|
||||
) -> Option<WebGlUniformLocation> {
|
||||
gl.get_uniform_location(&self.program, name)
|
||||
}
|
||||
|
||||
/// Set a mat3 uniform.
|
||||
/// 设置mat3 uniform。
|
||||
pub fn set_uniform_mat3(
|
||||
&self,
|
||||
gl: &WebGl2RenderingContext,
|
||||
name: &str,
|
||||
value: &[f32; 9],
|
||||
) {
|
||||
if let Some(location) = self.get_uniform_location(gl, name) {
|
||||
gl.uniform_matrix3fv_with_f32_array(Some(&location), false, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set an i32 uniform (for texture samplers).
|
||||
/// 设置i32 uniform(用于纹理采样器)。
|
||||
pub fn set_uniform_i32(
|
||||
&self,
|
||||
gl: &WebGl2RenderingContext,
|
||||
name: &str,
|
||||
value: i32,
|
||||
) {
|
||||
if let Some(location) = self.get_uniform_location(gl, name) {
|
||||
gl.uniform1i(Some(&location), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
packages/engine/src/renderer/texture/mod.rs
Normal file
8
packages/engine/src/renderer/texture/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
//! Texture management system.
|
||||
//! 纹理管理系统。
|
||||
|
||||
mod texture;
|
||||
mod texture_manager;
|
||||
|
||||
pub use texture::Texture;
|
||||
pub use texture_manager::TextureManager;
|
||||
39
packages/engine/src/renderer/texture/texture.rs
Normal file
39
packages/engine/src/renderer/texture/texture.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
//! Texture representation.
|
||||
//! 纹理表示。
|
||||
|
||||
use web_sys::WebGlTexture;
|
||||
|
||||
/// 2D texture.
|
||||
/// 2D纹理。
|
||||
pub struct Texture {
|
||||
/// WebGL texture handle.
|
||||
/// WebGL纹理句柄。
|
||||
pub(crate) handle: WebGlTexture,
|
||||
|
||||
/// Texture width in pixels.
|
||||
/// 纹理宽度(像素)。
|
||||
pub width: u32,
|
||||
|
||||
/// Texture height in pixels.
|
||||
/// 纹理高度(像素)。
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
/// Create a new texture.
|
||||
/// 创建新纹理。
|
||||
pub fn new(handle: WebGlTexture, width: u32, height: u32) -> Self {
|
||||
Self {
|
||||
handle,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the WebGL texture handle.
|
||||
/// 获取WebGL纹理句柄。
|
||||
#[inline]
|
||||
pub fn handle(&self) -> &WebGlTexture {
|
||||
&self.handle
|
||||
}
|
||||
}
|
||||
217
packages/engine/src/renderer/texture/texture_manager.rs
Normal file
217
packages/engine/src/renderer/texture/texture_manager.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
//! Texture loading and management.
|
||||
//! 纹理加载和管理。
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{HtmlImageElement, WebGl2RenderingContext, WebGlTexture};
|
||||
|
||||
use crate::core::error::{EngineError, Result};
|
||||
use super::Texture;
|
||||
|
||||
/// Texture manager for loading and caching textures.
|
||||
/// 用于加载和缓存纹理的纹理管理器。
|
||||
pub struct TextureManager {
|
||||
/// WebGL context.
|
||||
/// WebGL上下文。
|
||||
gl: WebGl2RenderingContext,
|
||||
|
||||
/// Loaded textures.
|
||||
/// 已加载的纹理。
|
||||
textures: HashMap<u32, Texture>,
|
||||
|
||||
/// Default white texture for untextured rendering.
|
||||
/// 用于无纹理渲染的默认白色纹理。
|
||||
default_texture: Option<WebGlTexture>,
|
||||
}
|
||||
|
||||
impl TextureManager {
|
||||
/// Create a new texture manager.
|
||||
/// 创建新的纹理管理器。
|
||||
pub fn new(gl: WebGl2RenderingContext) -> Self {
|
||||
let mut manager = Self {
|
||||
gl,
|
||||
textures: HashMap::new(),
|
||||
default_texture: None,
|
||||
};
|
||||
|
||||
// Create default white texture | 创建默认白色纹理
|
||||
manager.create_default_texture();
|
||||
|
||||
manager
|
||||
}
|
||||
|
||||
/// Create a 1x1 white texture as default.
|
||||
/// 创建1x1白色纹理作为默认纹理。
|
||||
fn create_default_texture(&mut self) {
|
||||
let texture = self.gl.create_texture();
|
||||
if let Some(tex) = &texture {
|
||||
self.gl.bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(tex));
|
||||
|
||||
let white_pixel: [u8; 4] = [255, 255, 255, 255];
|
||||
let _ = self.gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
WebGl2RenderingContext::TEXTURE_2D,
|
||||
0,
|
||||
WebGl2RenderingContext::RGBA as i32,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
WebGl2RenderingContext::RGBA,
|
||||
WebGl2RenderingContext::UNSIGNED_BYTE,
|
||||
Some(&white_pixel),
|
||||
);
|
||||
|
||||
self.gl.tex_parameteri(
|
||||
WebGl2RenderingContext::TEXTURE_2D,
|
||||
WebGl2RenderingContext::TEXTURE_MIN_FILTER,
|
||||
WebGl2RenderingContext::NEAREST as i32,
|
||||
);
|
||||
self.gl.tex_parameteri(
|
||||
WebGl2RenderingContext::TEXTURE_2D,
|
||||
WebGl2RenderingContext::TEXTURE_MAG_FILTER,
|
||||
WebGl2RenderingContext::NEAREST as i32,
|
||||
);
|
||||
}
|
||||
|
||||
self.default_texture = texture;
|
||||
}
|
||||
|
||||
/// Load a texture from URL.
|
||||
/// 从URL加载纹理。
|
||||
///
|
||||
/// Note: This is an async operation. The texture will be available
|
||||
/// after the image loads.
|
||||
/// 注意:这是一个异步操作。纹理在图片加载后可用。
|
||||
pub fn load_texture(&mut self, id: u32, url: &str) -> Result<()> {
|
||||
// Create placeholder texture | 创建占位纹理
|
||||
let texture = self.gl
|
||||
.create_texture()
|
||||
.ok_or_else(|| EngineError::TextureLoadFailed("Failed to create texture".into()))?;
|
||||
|
||||
// Set up temporary 1x1 texture | 设置临时1x1纹理
|
||||
self.gl.bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture));
|
||||
let placeholder: [u8; 4] = [128, 128, 128, 255];
|
||||
let _ = self.gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
WebGl2RenderingContext::TEXTURE_2D,
|
||||
0,
|
||||
WebGl2RenderingContext::RGBA as i32,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
WebGl2RenderingContext::RGBA,
|
||||
WebGl2RenderingContext::UNSIGNED_BYTE,
|
||||
Some(&placeholder),
|
||||
);
|
||||
|
||||
// Store texture with placeholder size | 存储带占位符尺寸的纹理
|
||||
self.textures.insert(id, Texture::new(texture.clone(), 1, 1));
|
||||
|
||||
// Load actual image asynchronously | 异步加载实际图片
|
||||
let gl = self.gl.clone();
|
||||
let texture_rc = Rc::new(RefCell::new(texture));
|
||||
let texture_clone = Rc::clone(&texture_rc);
|
||||
|
||||
// We need to update the stored texture size after loading
|
||||
// For MVP, we'll handle this through a callback mechanism
|
||||
// 加载后需要更新存储的纹理尺寸
|
||||
// 对于MVP,我们通过回调机制处理
|
||||
|
||||
let image = HtmlImageElement::new()
|
||||
.map_err(|_| EngineError::TextureLoadFailed("Failed to create image element".into()))?;
|
||||
|
||||
// Clone image for use in closure | 克隆图片用于闭包
|
||||
let image_clone = image.clone();
|
||||
|
||||
// Set up load callback | 设置加载回调
|
||||
let onload = Closure::wrap(Box::new(move || {
|
||||
let tex = texture_clone.borrow();
|
||||
gl.bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&tex));
|
||||
|
||||
// Use the captured image element | 使用捕获的图片元素
|
||||
let _ = gl.tex_image_2d_with_u32_and_u32_and_html_image_element(
|
||||
WebGl2RenderingContext::TEXTURE_2D,
|
||||
0,
|
||||
WebGl2RenderingContext::RGBA as i32,
|
||||
WebGl2RenderingContext::RGBA,
|
||||
WebGl2RenderingContext::UNSIGNED_BYTE,
|
||||
&image_clone,
|
||||
);
|
||||
|
||||
// Set texture parameters | 设置纹理参数
|
||||
gl.tex_parameteri(
|
||||
WebGl2RenderingContext::TEXTURE_2D,
|
||||
WebGl2RenderingContext::TEXTURE_WRAP_S,
|
||||
WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
|
||||
);
|
||||
gl.tex_parameteri(
|
||||
WebGl2RenderingContext::TEXTURE_2D,
|
||||
WebGl2RenderingContext::TEXTURE_WRAP_T,
|
||||
WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
|
||||
);
|
||||
gl.tex_parameteri(
|
||||
WebGl2RenderingContext::TEXTURE_2D,
|
||||
WebGl2RenderingContext::TEXTURE_MIN_FILTER,
|
||||
WebGl2RenderingContext::LINEAR as i32,
|
||||
);
|
||||
gl.tex_parameteri(
|
||||
WebGl2RenderingContext::TEXTURE_2D,
|
||||
WebGl2RenderingContext::TEXTURE_MAG_FILTER,
|
||||
WebGl2RenderingContext::LINEAR as i32,
|
||||
);
|
||||
|
||||
log::debug!("Texture loaded | 纹理加载完成");
|
||||
}) as Box<dyn Fn()>);
|
||||
|
||||
image.set_onload(Some(onload.as_ref().unchecked_ref()));
|
||||
onload.forget(); // Prevent closure from being dropped | 防止闭包被销毁
|
||||
|
||||
image.set_src(url);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get texture by ID.
|
||||
/// 按ID获取纹理。
|
||||
#[inline]
|
||||
pub fn get_texture(&self, id: u32) -> Option<&Texture> {
|
||||
self.textures.get(&id)
|
||||
}
|
||||
|
||||
/// Get texture size by ID.
|
||||
/// 按ID获取纹理尺寸。
|
||||
#[inline]
|
||||
pub fn get_texture_size(&self, id: u32) -> Option<(f32, f32)> {
|
||||
self.textures
|
||||
.get(&id)
|
||||
.map(|t| (t.width as f32, t.height as f32))
|
||||
}
|
||||
|
||||
/// Bind texture for rendering.
|
||||
/// 绑定纹理用于渲染。
|
||||
pub fn bind_texture(&self, id: u32, slot: u32) {
|
||||
self.gl.active_texture(WebGl2RenderingContext::TEXTURE0 + slot);
|
||||
|
||||
if let Some(texture) = self.textures.get(&id) {
|
||||
self.gl.bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture.handle));
|
||||
} else if let Some(default) = &self.default_texture {
|
||||
self.gl.bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(default));
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if texture is loaded.
|
||||
/// 检查纹理是否已加载。
|
||||
#[inline]
|
||||
pub fn has_texture(&self, id: u32) -> bool {
|
||||
self.textures.contains_key(&id)
|
||||
}
|
||||
|
||||
/// Remove texture.
|
||||
/// 移除纹理。
|
||||
pub fn remove_texture(&mut self, id: u32) {
|
||||
if let Some(texture) = self.textures.remove(&id) {
|
||||
self.gl.delete_texture(Some(&texture.handle));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user