Files
esengine/packages/rust/engine-shared/src/batch/sprite_data.rs
YHH 155411e743 refactor: reorganize package structure and decouple framework packages (#338)
* 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
2025-12-26 14:50:35 +08:00

418 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 精灵批处理数据结构
//!
//! 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());
}
}