Files
esengine/packages/editor/plugins/particle-editor/src/gizmos/ParticleGizmo.ts
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

314 lines
11 KiB
TypeScript

/**
* Particle System Gizmo Implementation
* 粒子系统 Gizmo 实现
*
* 显示粒子发射区域形状,支持 Transform 缩放和旋转。
* Displays particle emission shape, supports Transform scale and rotation.
*/
import type { Entity } from '@esengine/ecs-framework';
import type {
IGizmoRenderData,
IRectGizmoData,
ICircleGizmoData,
ILineGizmoData,
GizmoColor
} from '@esengine/editor-core';
import { GizmoRegistry } from '@esengine/editor-core';
import { TransformComponent } from '@esengine/engine-core';
import { ParticleSystemComponent, EmissionShape } from '@esengine/particle';
/**
* 粒子 Gizmo 颜色配置
* Particle gizmo color configuration
*/
const ParticleGizmoColors = {
emissionShape: { r: 1.0, g: 0.6, b: 0.0, a: 0.8 } as GizmoColor,
emissionShapeSelected: { r: 1.0, g: 0.8, b: 0.2, a: 1.0 } as GizmoColor,
direction: { r: 0.0, g: 0.8, b: 1.0, a: 0.9 } as GizmoColor,
centerMark: { r: 1.0, g: 1.0, b: 1.0, a: 0.8 } as GizmoColor,
};
/**
* 创建中心点标记 Gizmo (十字形)
* Create center mark gizmo (cross shape)
*/
function createCenterMarkGizmo(x: number, y: number, size: number, color: GizmoColor): ILineGizmoData[] {
const halfSize = size / 2;
return [
{
type: 'line',
points: [
{ x: x - halfSize, y: y },
{ x: x + halfSize, y: y }
],
color,
closed: false
},
{
type: 'line',
points: [
{ x: x, y: y - halfSize },
{ x: x, y: y + halfSize }
],
color,
closed: false
}
];
}
/**
* 创建方向箭头
* Create direction arrow
*/
function createDirectionArrow(
x: number,
y: number,
direction: number,
length: number,
color: GizmoColor
): ILineGizmoData[] {
const endX = x + Math.cos(direction) * length;
const endY = y + Math.sin(direction) * length;
const arrowSize = length * 0.2;
const arrowAngle = Math.PI / 6;
return [
// 主线
{
type: 'line',
points: [
{ x, y },
{ x: endX, y: endY }
],
color,
closed: false
},
// 箭头左
{
type: 'line',
points: [
{ x: endX, y: endY },
{
x: endX - arrowSize * Math.cos(direction - arrowAngle),
y: endY - arrowSize * Math.sin(direction - arrowAngle)
}
],
color,
closed: false
},
// 箭头右
{
type: 'line',
points: [
{ x: endX, y: endY },
{
x: endX - arrowSize * Math.cos(direction + arrowAngle),
y: endY - arrowSize * Math.sin(direction + arrowAngle)
}
],
color,
closed: false
}
];
}
/**
* ParticleSystemComponent gizmo provider
* 粒子系统组件 gizmo 提供者
*/
function particleSystemGizmoProvider(
particle: ParticleSystemComponent,
entity: Entity,
isSelected: boolean
): IGizmoRenderData[] {
const transform = entity.getComponent(TransformComponent);
if (!transform) return [];
const gizmos: IGizmoRenderData[] = [];
const color = isSelected
? ParticleGizmoColors.emissionShapeSelected
: ParticleGizmoColors.emissionShape;
// 获取 Transform 数据 | Get transform data
const worldX = transform.worldPosition?.x ?? transform.position.x;
const worldY = transform.worldPosition?.y ?? transform.position.y;
const rot = transform.worldRotation ?? transform.rotation;
const worldRotation = rot?.z ?? 0;
const scale = transform.worldScale ?? transform.scale;
const scaleX = scale?.x ?? 1;
const scaleY = scale?.y ?? 1;
// 从加载的资产获取发射形状配置 | Get emission shape config from loaded asset
const asset = particle.loadedAsset;
const emissionShape = asset?.emissionShape ?? EmissionShape.Point;
const shapeRadius = (asset?.shapeRadius ?? 0) * Math.max(scaleX, scaleY);
const shapeWidth = (asset?.shapeWidth ?? 0) * scaleX;
const shapeHeight = (asset?.shapeHeight ?? 0) * scaleY;
const shapeAngle = (asset?.shapeAngle ?? 30) * Math.PI / 180; // 转换为弧度
// 转换为弧度并应用世界旋转 | Convert to radians and apply world rotation
// worldRotation 是度(顺时针),转为弧度(逆时针)用于数学计算
// worldRotation is degrees(clockwise), convert to radians(counter-clockwise) for math
const direction = ((asset?.direction ?? 90) * Math.PI / 180) - (worldRotation * Math.PI / 180);
// 根据发射形状绘制 Gizmo | Draw gizmo based on emission shape
switch (emissionShape) {
case EmissionShape.Point:
// 点发射:显示中心点和方向 | Point: show center and direction
gizmos.push(...createCenterMarkGizmo(worldX, worldY, 10, color));
if (isSelected) {
gizmos.push(...createDirectionArrow(worldX, worldY, direction, 30, ParticleGizmoColors.direction));
}
break;
case EmissionShape.Circle:
// 填充圆形:显示圆形区域 | Filled circle: show circle area
gizmos.push({
type: 'circle',
x: worldX,
y: worldY,
radius: shapeRadius || 20,
color
} as ICircleGizmoData);
if (isSelected) {
gizmos.push(...createCenterMarkGizmo(worldX, worldY, 8, ParticleGizmoColors.centerMark));
gizmos.push(...createDirectionArrow(worldX, worldY, direction, shapeRadius + 20 || 40, ParticleGizmoColors.direction));
}
break;
case EmissionShape.Ring:
// 圆环:显示圆环边缘 | Ring: show ring edge
gizmos.push({
type: 'circle',
x: worldX,
y: worldY,
radius: shapeRadius || 20,
color
} as ICircleGizmoData);
if (isSelected) {
gizmos.push(...createCenterMarkGizmo(worldX, worldY, 8, ParticleGizmoColors.centerMark));
}
break;
case EmissionShape.Rectangle:
// 矩形:显示矩形区域 | Rectangle: show rect area
gizmos.push({
type: 'rect',
x: worldX,
y: worldY,
width: shapeWidth || 40,
height: shapeHeight || 20,
rotation: worldRotation,
originX: 0.5,
originY: 0.5,
color,
showHandles: false
} as IRectGizmoData);
if (isSelected) {
gizmos.push(...createCenterMarkGizmo(worldX, worldY, 8, ParticleGizmoColors.centerMark));
gizmos.push(...createDirectionArrow(worldX, worldY, direction, Math.max(shapeWidth, shapeHeight) / 2 + 20 || 40, ParticleGizmoColors.direction));
}
break;
case EmissionShape.Edge:
// 矩形边缘:显示矩形边框 | Rectangle edge: show rect border
gizmos.push({
type: 'rect',
x: worldX,
y: worldY,
width: shapeWidth || 40,
height: shapeHeight || 20,
rotation: worldRotation,
originX: 0.5,
originY: 0.5,
color,
showHandles: false
} as IRectGizmoData);
if (isSelected) {
gizmos.push(...createCenterMarkGizmo(worldX, worldY, 8, ParticleGizmoColors.centerMark));
}
break;
case EmissionShape.Line:
// 线段:显示发射线 | Line: show emission line
{
const halfWidth = (shapeWidth || 40) / 2;
const cos = Math.cos(direction + Math.PI / 2);
const sin = Math.sin(direction + Math.PI / 2);
gizmos.push({
type: 'line',
points: [
{ x: worldX + cos * halfWidth, y: worldY + sin * halfWidth },
{ x: worldX - cos * halfWidth, y: worldY - sin * halfWidth }
],
color,
closed: false
} as ILineGizmoData);
if (isSelected) {
gizmos.push(...createCenterMarkGizmo(worldX, worldY, 8, ParticleGizmoColors.centerMark));
gizmos.push(...createDirectionArrow(worldX, worldY, direction, halfWidth + 20, ParticleGizmoColors.direction));
}
}
break;
case EmissionShape.Cone:
// 圆锥/扇形:显示扇形区域 | Cone/fan: show fan area
{
const radius = shapeRadius || 30;
const halfAngle = shapeAngle / 2;
const startAngle = direction - halfAngle;
const endAngle = direction + halfAngle;
// 绘制扇形的两条边和弧线 | Draw two edges and arc of the fan
const segments = 16;
const points: { x: number; y: number }[] = [{ x: worldX, y: worldY }];
for (let i = 0; i <= segments; i++) {
const angle = startAngle + (endAngle - startAngle) * (i / segments);
points.push({
x: worldX + Math.cos(angle) * radius,
y: worldY + Math.sin(angle) * radius
});
}
points.push({ x: worldX, y: worldY });
gizmos.push({
type: 'line',
points,
color,
closed: true
} as ILineGizmoData);
if (isSelected) {
gizmos.push(...createCenterMarkGizmo(worldX, worldY, 8, ParticleGizmoColors.centerMark));
gizmos.push(...createDirectionArrow(worldX, worldY, direction, radius + 20, ParticleGizmoColors.direction));
}
}
break;
default:
// 默认:显示中心点 | Default: show center point
gizmos.push(...createCenterMarkGizmo(worldX, worldY, 10, color));
break;
}
return gizmos;
}
/**
* Register gizmo provider for ParticleSystemComponent.
* 为 ParticleSystemComponent 注册 gizmo 提供者。
*/
export function registerParticleGizmo(): void {
GizmoRegistry.register(ParticleSystemComponent, particleSystemGizmoProvider);
}
/**
* Unregister gizmo provider for ParticleSystemComponent.
* 取消注册 ParticleSystemComponent 的 gizmo 提供者。
*/
export function unregisterParticleGizmo(): void {
GizmoRegistry.unregister(ParticleSystemComponent);
}