* 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
418 lines
12 KiB
Rust
418 lines
12 KiB
Rust
//! 精灵批处理数据结构
|
||
//!
|
||
//! Sprite batch data structures.
|
||
//!
|
||
//! 本模块提供纯数据结构,不包含任何渲染调用。
|
||
//! This module provides pure data structures without any rendering calls.
|
||
|
||
use crate::types::vertex::SpriteVertex;
|
||
|
||
/// 批处理键
|
||
///
|
||
/// 用于区分不同批次(按材质和纹理分组)。
|
||
///
|
||
/// Batch key.
|
||
/// Used to distinguish different batches (grouped by material and texture).
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||
pub struct BatchKey {
|
||
/// 材质 ID | Material ID
|
||
pub material_id: u32,
|
||
/// 纹理 ID | Texture ID
|
||
pub texture_id: u32,
|
||
}
|
||
|
||
impl BatchKey {
|
||
/// 创建新的批处理键
|
||
///
|
||
/// Create new batch key.
|
||
pub const fn new(material_id: u32, texture_id: u32) -> Self {
|
||
Self { material_id, texture_id }
|
||
}
|
||
|
||
/// 默认批处理键(默认材质和纹理)
|
||
///
|
||
/// Default batch key (default material and texture).
|
||
pub const fn default_key() -> Self {
|
||
Self::new(0, 0)
|
||
}
|
||
}
|
||
|
||
impl Default for BatchKey {
|
||
fn default() -> Self {
|
||
Self::default_key()
|
||
}
|
||
}
|
||
|
||
/// 精灵批处理数据(纯数据,无渲染调用)
|
||
///
|
||
/// 预分配的数组用于存储精灵顶点数据,避免每帧分配。
|
||
///
|
||
/// Sprite batch data (pure data, no rendering calls).
|
||
/// Pre-allocated arrays for storing sprite vertex data, avoiding per-frame allocation.
|
||
#[derive(Debug)]
|
||
pub struct SpriteBatchBuffer {
|
||
/// 顶点数据 | Vertex data
|
||
vertices: Vec<SpriteVertex>,
|
||
|
||
/// 索引数据 | Index data
|
||
indices: Vec<u16>,
|
||
|
||
/// 批次列表:(BatchKey, 起始索引, 索引数量) | Batch list: (BatchKey, start index, index count)
|
||
batches: Vec<(BatchKey, u32, u32)>,
|
||
|
||
/// 最大精灵数 | Max sprite count
|
||
max_sprites: usize,
|
||
|
||
/// 当前精灵数 | Current sprite count
|
||
sprite_count: usize,
|
||
|
||
/// 上一个批处理键 | Last batch key
|
||
last_batch_key: Option<BatchKey>,
|
||
}
|
||
|
||
impl SpriteBatchBuffer {
|
||
/// 每个精灵的顶点数 | Vertices per sprite
|
||
pub const VERTICES_PER_SPRITE: usize = 4;
|
||
|
||
/// 每个精灵的索引数 | Indices per sprite
|
||
pub const INDICES_PER_SPRITE: usize = 6;
|
||
|
||
/// 创建新的批处理缓冲区
|
||
///
|
||
/// Create new batch buffer.
|
||
pub fn new(max_sprites: usize) -> Self {
|
||
let max_vertices = max_sprites * Self::VERTICES_PER_SPRITE;
|
||
let max_indices = max_sprites * Self::INDICES_PER_SPRITE;
|
||
|
||
// 预生成索引
|
||
let mut indices = Vec::with_capacity(max_indices);
|
||
for i in 0..max_sprites {
|
||
let base = (i * Self::VERTICES_PER_SPRITE) as u16;
|
||
indices.extend_from_slice(&[
|
||
base,
|
||
base + 1,
|
||
base + 2,
|
||
base + 2,
|
||
base + 3,
|
||
base,
|
||
]);
|
||
}
|
||
|
||
Self {
|
||
vertices: Vec::with_capacity(max_vertices),
|
||
indices,
|
||
batches: Vec::with_capacity(64),
|
||
max_sprites,
|
||
sprite_count: 0,
|
||
last_batch_key: None,
|
||
}
|
||
}
|
||
|
||
/// 清空缓冲区(为下一帧准备)
|
||
///
|
||
/// Clear buffer (prepare for next frame).
|
||
pub fn clear(&mut self) {
|
||
self.vertices.clear();
|
||
self.batches.clear();
|
||
self.sprite_count = 0;
|
||
self.last_batch_key = None;
|
||
}
|
||
|
||
/// 添加精灵
|
||
///
|
||
/// Add sprite.
|
||
///
|
||
/// # Parameters
|
||
///
|
||
/// - `x`, `y`: 位置 | Position
|
||
/// - `width`, `height`: 尺寸 | Size
|
||
/// - `rotation`: 旋转(弧度)| Rotation (radians)
|
||
/// - `origin_x`, `origin_y`: 原点(0-1)| Origin (0-1)
|
||
/// - `u0`, `v0`, `u1`, `v1`: UV 坐标 | UV coordinates
|
||
/// - `color`: 打包的 RGBA 颜色 | Packed RGBA color
|
||
/// - `texture_id`: 纹理 ID | Texture ID
|
||
/// - `material_id`: 材质 ID | Material ID
|
||
#[allow(clippy::too_many_arguments)]
|
||
pub fn add_sprite(
|
||
&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: u32,
|
||
texture_id: u32,
|
||
material_id: u32,
|
||
) -> bool {
|
||
if self.sprite_count >= self.max_sprites {
|
||
return false;
|
||
}
|
||
|
||
// 解包颜色
|
||
let r = ((color >> 24) & 0xFF) as f32 / 255.0;
|
||
let g = ((color >> 16) & 0xFF) as f32 / 255.0;
|
||
let b = ((color >> 8) & 0xFF) as f32 / 255.0;
|
||
let a = (color & 0xFF) as f32 / 255.0;
|
||
let color_arr = [r, g, b, a];
|
||
|
||
// 计算宽高比
|
||
let aspect = if height != 0.0 { width / height } else { 1.0 };
|
||
|
||
// 计算顶点位置(考虑原点和旋转)
|
||
let ox = origin_x * width;
|
||
let oy = origin_y * height;
|
||
|
||
let cos_r = rotation.cos();
|
||
let sin_r = rotation.sin();
|
||
|
||
// 四个角的局部坐标
|
||
let corners = [
|
||
(-ox, -oy), // 左上
|
||
(width - ox, -oy), // 右上
|
||
(width - ox, height - oy), // 右下
|
||
(-ox, height - oy), // 左下
|
||
];
|
||
|
||
// UV 坐标
|
||
let uvs = [
|
||
[u0, v0], // 左上
|
||
[u1, v0], // 右上
|
||
[u1, v1], // 右下
|
||
[u0, v1], // 左下
|
||
];
|
||
|
||
// 添加四个顶点
|
||
for i in 0..4 {
|
||
let (lx, ly) = corners[i];
|
||
let rx = lx * cos_r - ly * sin_r + x;
|
||
let ry = lx * sin_r + ly * cos_r + y;
|
||
|
||
self.vertices.push(SpriteVertex {
|
||
position: [rx, ry],
|
||
texcoord: uvs[i],
|
||
color: color_arr,
|
||
aspect,
|
||
});
|
||
}
|
||
|
||
// 更新批次
|
||
let key = BatchKey::new(material_id, texture_id);
|
||
if self.last_batch_key != Some(key) {
|
||
// 开始新批次
|
||
let start_index = (self.sprite_count * Self::INDICES_PER_SPRITE) as u32;
|
||
self.batches.push((key, start_index, Self::INDICES_PER_SPRITE as u32));
|
||
self.last_batch_key = Some(key);
|
||
} else {
|
||
// 扩展当前批次
|
||
if let Some((_, _, count)) = self.batches.last_mut() {
|
||
*count += Self::INDICES_PER_SPRITE as u32;
|
||
}
|
||
}
|
||
|
||
self.sprite_count += 1;
|
||
true
|
||
}
|
||
|
||
/// 从 SoA 数据添加精灵(与现有 API 兼容)
|
||
///
|
||
/// Add sprites from SoA data (compatible with existing API).
|
||
///
|
||
/// # Parameters
|
||
///
|
||
/// - `transforms`: [x, y, rotation, scaleX, scaleY, originX, originY] per sprite
|
||
/// - `texture_ids`: 纹理 ID | Texture IDs
|
||
/// - `uvs`: [u0, v0, u1, v1] per sprite
|
||
/// - `colors`: 打包的 RGBA 颜色 | Packed RGBA colors
|
||
/// - `material_ids`: 材质 ID | Material IDs
|
||
/// - `texture_sizes`: 纹理尺寸映射函数 | Texture size lookup function
|
||
pub fn add_sprites_soa<F>(
|
||
&mut self,
|
||
transforms: &[f32],
|
||
texture_ids: &[u32],
|
||
uvs: &[f32],
|
||
colors: &[u32],
|
||
material_ids: &[u32],
|
||
texture_sizes: F,
|
||
) -> usize
|
||
where
|
||
F: Fn(u32) -> (f32, f32),
|
||
{
|
||
let count = texture_ids.len();
|
||
let mut added = 0;
|
||
|
||
for i in 0..count {
|
||
let t_offset = i * 7;
|
||
let uv_offset = i * 4;
|
||
|
||
if t_offset + 6 >= transforms.len() || uv_offset + 3 >= uvs.len() {
|
||
break;
|
||
}
|
||
|
||
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 texture_id = texture_ids[i];
|
||
let (tex_width, tex_height) = texture_sizes(texture_id);
|
||
|
||
let width = tex_width * scale_x;
|
||
let height = tex_height * scale_y;
|
||
|
||
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 = colors[i];
|
||
let material_id = material_ids[i];
|
||
|
||
if self.add_sprite(
|
||
x, y, width, height, rotation, origin_x, origin_y,
|
||
u0, v0, u1, v1, color, texture_id, material_id,
|
||
) {
|
||
added += 1;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
|
||
added
|
||
}
|
||
|
||
/// 获取顶点数据
|
||
///
|
||
/// Get vertex data.
|
||
pub fn vertices(&self) -> &[SpriteVertex] {
|
||
&self.vertices
|
||
}
|
||
|
||
/// 获取顶点数据(字节)
|
||
///
|
||
/// Get vertex data as bytes.
|
||
pub fn vertices_as_bytes(&self) -> &[u8] {
|
||
bytemuck::cast_slice(&self.vertices)
|
||
}
|
||
|
||
/// 获取索引数据
|
||
///
|
||
/// Get index data.
|
||
pub fn indices(&self) -> &[u16] {
|
||
&self.indices[..self.sprite_count * Self::INDICES_PER_SPRITE]
|
||
}
|
||
|
||
/// 获取批次列表
|
||
///
|
||
/// Get batch list.
|
||
pub fn batches(&self) -> &[(BatchKey, u32, u32)] {
|
||
&self.batches
|
||
}
|
||
|
||
/// 获取精灵数量
|
||
///
|
||
/// Get sprite count.
|
||
pub fn sprite_count(&self) -> usize {
|
||
self.sprite_count
|
||
}
|
||
|
||
/// 获取最大精灵数
|
||
///
|
||
/// Get max sprite count.
|
||
pub fn max_sprites(&self) -> usize {
|
||
self.max_sprites
|
||
}
|
||
|
||
/// 是否为空
|
||
///
|
||
/// Check if empty.
|
||
pub fn is_empty(&self) -> bool {
|
||
self.sprite_count == 0
|
||
}
|
||
|
||
/// 是否已满
|
||
///
|
||
/// Check if full.
|
||
pub fn is_full(&self) -> bool {
|
||
self.sprite_count >= self.max_sprites
|
||
}
|
||
}
|
||
|
||
impl Default for SpriteBatchBuffer {
|
||
fn default() -> Self {
|
||
Self::new(10000)
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_batch_buffer_creation() {
|
||
let buffer = SpriteBatchBuffer::new(100);
|
||
assert_eq!(buffer.max_sprites(), 100);
|
||
assert_eq!(buffer.sprite_count(), 0);
|
||
assert!(buffer.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn test_add_sprite() {
|
||
let mut buffer = SpriteBatchBuffer::new(100);
|
||
|
||
let result = buffer.add_sprite(
|
||
100.0, 100.0, // position
|
||
64.0, 64.0, // size
|
||
0.0, // rotation
|
||
0.5, 0.5, // origin
|
||
0.0, 0.0, 1.0, 1.0, // uvs
|
||
0xFFFFFFFF, // color (white)
|
||
1, // texture_id
|
||
0, // material_id
|
||
);
|
||
|
||
assert!(result);
|
||
assert_eq!(buffer.sprite_count(), 1);
|
||
assert_eq!(buffer.vertices().len(), 4);
|
||
assert_eq!(buffer.batches().len(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn test_batch_grouping() {
|
||
let mut buffer = SpriteBatchBuffer::new(100);
|
||
|
||
// 添加两个相同纹理/材质的精灵(应该合并到一个批次)
|
||
buffer.add_sprite(0.0, 0.0, 32.0, 32.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0xFFFFFFFF, 1, 0);
|
||
buffer.add_sprite(50.0, 0.0, 32.0, 32.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0xFFFFFFFF, 1, 0);
|
||
|
||
assert_eq!(buffer.batches().len(), 1);
|
||
assert_eq!(buffer.batches()[0].2, 12); // 2 sprites * 6 indices
|
||
|
||
// 添加不同纹理的精灵(应该创建新批次)
|
||
buffer.add_sprite(100.0, 0.0, 32.0, 32.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0xFFFFFFFF, 2, 0);
|
||
|
||
assert_eq!(buffer.batches().len(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn test_clear() {
|
||
let mut buffer = SpriteBatchBuffer::new(100);
|
||
|
||
buffer.add_sprite(0.0, 0.0, 32.0, 32.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0xFFFFFFFF, 1, 0);
|
||
assert_eq!(buffer.sprite_count(), 1);
|
||
|
||
buffer.clear();
|
||
assert_eq!(buffer.sprite_count(), 0);
|
||
assert!(buffer.is_empty());
|
||
assert!(buffer.batches().is_empty());
|
||
}
|
||
}
|