Files
esengine/packages/math/src/Vector3.ts
YHH beaa1d09de feat: 预制体系统与架构改进 (#303)
* feat(prefab): 实现预制体系统和编辑器 UX 改进

## 预制体系统
- 新增 PrefabSerializer: 预制体序列化/反序列化
- 新增 PrefabInstanceComponent: 追踪预制体实例来源和修改
- 新增 PrefabService: 预制体核心服务
- 新增 PrefabLoader: 预制体资产加载器
- 新增预制体命令: Create/Instantiate/Apply/Revert/BreakLink

## 预制体编辑模式
- 支持双击 .prefab 文件进入编辑模式
- 预制体编辑模式工具栏 (保存/退出)
- 预制体实例指示器和操作菜单

## 编辑器 UX 改进
- SceneHierarchy 快捷键: F2 重命名, Ctrl+D 复制, ↑↓ 导航
- 支持双击实体名称内联编辑
- 删除实体时显示子节点数量警告
- 右键菜单添加重命名/复制选项及快捷键提示
- 布局持久化和重置功能

## Bug 修复
- 修复 editor-runtime 组件类重复导致的 TransformComponent 不识别问题
- 修复 .prefab-name 样式覆盖导致预制体工具栏文字不可见
- 修复 Inspector 资源字段高度不正确问题

* feat(editor): 改进编辑器 UX 交互体验

- ContentBrowser: 加载动画 spinner、搜索高亮、改进空状态设计
- SceneHierarchy: 选中项自动滚动到视图、搜索清除按钮
- PropertyInspector: 输入框本地状态管理、Enter/Escape 键处理
- EntityInspector: 组件折叠状态持久化、属性搜索清除按钮
- Viewport: 变换操作实时数值显示
- 国际化: 添加相关文本 (en/zh)

* fix(build): 修复 Web 构建资产加载和编辑器 UX 改进

构建系统修复:
- 修复 asset-catalog.json 字段名不匹配 (entries vs assets)
- 修复 BrowserFileSystemService 支持两种目录格式
- 修复 bundle 策略检测逻辑 (空对象判断)
- 修复 module.json 中 assetExtensions 声明和类型推断

行为树修复:
- 修复 BehaviorTreeExecutionSystem 使用 loadAsset 替代 loadAssetByPath
- 修复 BehaviorTreeAssetType 常量与 module.json 类型名一致 (behavior-tree)

编辑器 UX 改进:
- 构建完成对话框添加"打开文件夹"按钮
- 构建完成对话框样式优化 (圆形图标背景、按钮布局)
- SceneHierarchy 响应式布局 (窄窗口自动隐藏 Type 列)
- SceneHierarchy 隐藏滚动条

错误追踪:
- 添加全局错误处理器写入日志文件 (%TEMP%/esengine-editor-crash.log)
- 添加 append_to_log Tauri 命令

* feat(render): 修复 UI 渲染和点击特效系统

## UI 渲染修复
- 修复 GUID 验证 bug,使用统一的 isValidGUID() 函数
- 修复 UI 渲染顺序随机问题,Rust 端使用 IndexMap 替代 HashMap
- Web 运行时添加 assetPathResolver 支持 GUID 解析
- UIInteractableComponent.blockEvents 默认值改为 false

## 点击特效系统
- 新增 ClickFxComponent 和 ClickFxSystem
- 支持在点击位置播放粒子效果
- 支持多种触发模式和粒子轮换

## Camera 系统重构
- CameraSystem 从 ecs-engine-bindgen 移至 camera 包
- 新增 CameraManager 统一管理相机

## 编辑器改进
- 改进属性面板 UI 交互
- 粒子编辑器面板优化
- Transform 命令系统

* feat(render): 实现 Sorting Layer 系统和 Overlay 渲染层

- 新增 SortingLayerManager 管理排序层级 (Background, Default, Foreground, UI, Overlay)
- 实现 ISortable 接口,统一 Sprite、UI、Particle 的排序属性
- 修复粒子 Overlay 层被 UI 遮挡问题:添加独立的 Overlay Pass 在 UI 之后渲染
- 更新粒子资产格式:从 sortingOrder 改为 sortingLayer + orderInLayer
- 更新粒子编辑器面板支持新的排序属性
- 优化 UI 渲染系统使用新的排序层级

* feat(ci): 集成 SignPath 代码签名服务

- 添加 SignPath 自动签名工作流(Windows)
- 配置 release-editor.yml 支持代码签名
- 将构建改为草稿模式,等待签名完成后发布
- 添加证书文件到 .gitignore 防止泄露

* fix(asset): 修复 Web 构建资产路径解析和全局单例移除

## 资产路径修复
- 修复 Tauri 本地服务器 `/asset?path=...` 路径解析,正确与 root 目录连接
- BrowserPathResolver 支持两种模式:
  - 'proxy': 使用 /asset?path=... 格式(编辑器 Run in Browser)
  - 'direct': 使用直接路径 /assets/path.png(独立 Web 构建)
- BrowserRuntime 使用 'direct' 模式,无需 Tauri 代理

## 架构改进 - 移除全局单例
- 移除 globalAssetManager 导出,改用 AssetManagerToken 依赖注入
- 移除 globalPathResolver 导出,改用 PathResolutionService
- 移除 globalPathResolutionService 导出
- ParticleUpdateSystem/ClickFxSystem 通过 setAssetManager() 注入依赖
- EngineService 使用 new AssetManager() 替代全局实例

## 新增服务
- PathResolutionService: 统一路径解析接口
- RuntimeModeService: 运行时模式查询服务
- SerializationContext: EntityRef 序列化上下文

## 其他改进
- 完善 ServiceToken 注释说明本地定义的意图
- 导出 BrowserPathResolveMode 类型

* fix(build): 添加 world-streaming composite 设置修复类型检查

* fix(build): 移除 world-streaming 引用避免 composite 冲突

* fix(build): 将 const enum 改为 enum 兼容 isolatedModules

* fix(build): 添加缺失的 IAssetManager 导入
2025-12-13 19:44:08 +08:00

453 lines
11 KiB
TypeScript
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.
/**
* 3D 向量数据接口
*
* 轻量级数据结构,用于组件属性和序列化。
* Lightweight data structure for component properties and serialization.
*/
export interface IVector3 {
x: number;
y: number;
z: number;
}
/**
* 3D向量类
*
* 提供完整的3D向量运算功能包括
* - 基础运算(加减乘除)
* - 向量运算(点积、叉积、归一化)
* - 几何运算(距离、角度、投影)
* - 变换操作(旋转、反射、插值)
*/
export class Vector3 implements IVector3 {
/** X分量 */
public x: number;
/** Y分量 */
public y: number;
/** Z分量 */
public z: number;
/**
* 创建3D向量
* @param x X分量默认为0
* @param y Y分量默认为0
* @param z Z分量默认为0
*/
constructor(x: number = 0, y: number = 0, z: number = 0) {
this.x = x;
this.y = y;
this.z = z;
}
// 静态常量
/** 零向量 (0, 0, 0) */
static readonly ZERO = new Vector3(0, 0, 0);
/** 单位向量 (1, 1, 1) */
static readonly ONE = new Vector3(1, 1, 1);
/** 右方向向量 (1, 0, 0) */
static readonly RIGHT = new Vector3(1, 0, 0);
/** 左方向向量 (-1, 0, 0) */
static readonly LEFT = new Vector3(-1, 0, 0);
/** 上方向向量 (0, 1, 0) */
static readonly UP = new Vector3(0, 1, 0);
/** 下方向向量 (0, -1, 0) */
static readonly DOWN = new Vector3(0, -1, 0);
/** 前方向向量 (0, 0, 1) */
static readonly FORWARD = new Vector3(0, 0, 1);
/** 后方向向量 (0, 0, -1) */
static readonly BACK = new Vector3(0, 0, -1);
// 基础属性
/**
* 获取向量长度(模)
*/
get length(): number {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
/**
* 获取向量长度的平方
*/
get lengthSquared(): number {
return this.x * this.x + this.y * this.y + this.z * this.z;
}
/**
* 检查是否为零向量
*/
get isZero(): boolean {
return this.x === 0 && this.y === 0 && this.z === 0;
}
/**
* 检查是否为单位向量
*/
get isUnit(): boolean {
const lenSq = this.lengthSquared;
return Math.abs(lenSq - 1) < Number.EPSILON;
}
// 基础运算
/**
* 设置向量分量
* @param x X分量
* @param y Y分量
* @param z Z分量
* @returns 当前向量实例(链式调用)
*/
set(x: number, y: number, z: number): this {
this.x = x;
this.y = y;
this.z = z;
return this;
}
/**
* 复制另一个向量的值
* @param other 源向量
* @returns 当前向量实例(链式调用)
*/
copy(other: Vector3): this {
this.x = other.x;
this.y = other.y;
this.z = other.z;
return this;
}
/**
* 克隆当前向量
* @returns 新的向量实例
*/
clone(): Vector3 {
return new Vector3(this.x, this.y, this.z);
}
/**
* 向量加法
* @param other 另一个向量
* @returns 当前向量实例(链式调用)
*/
add(other: Vector3): this {
this.x += other.x;
this.y += other.y;
this.z += other.z;
return this;
}
/**
* 向量减法
* @param other 另一个向量
* @returns 当前向量实例(链式调用)
*/
subtract(other: Vector3): this {
this.x -= other.x;
this.y -= other.y;
this.z -= other.z;
return this;
}
/**
* 向量数乘
* @param scalar 标量
* @returns 当前向量实例(链式调用)
*/
multiply(scalar: number): this {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
return this;
}
/**
* 向量数除
* @param scalar 标量
* @returns 当前向量实例(链式调用)
*/
divide(scalar: number): this {
if (scalar === 0) {
throw new Error('不能除以零');
}
this.x /= scalar;
this.y /= scalar;
this.z /= scalar;
return this;
}
/**
* 向量取反
* @returns 当前向量实例(链式调用)
*/
negate(): this {
this.x = -this.x;
this.y = -this.y;
this.z = -this.z;
return this;
}
// 向量运算
/**
* 计算与另一个向量的点积
* @param other 另一个向量
* @returns 点积值
*/
dot(other: Vector3): number {
return this.x * other.x + this.y * other.y + this.z * other.z;
}
/**
* 计算与另一个向量的叉积
* @param other 另一个向量
* @returns 新的叉积向量
*/
cross(other: Vector3): Vector3 {
return new Vector3(
this.y * other.z - this.z * other.y,
this.z * other.x - this.x * other.z,
this.x * other.y - this.y * other.x
);
}
/**
* 向量归一化(转换为单位向量)
* @returns 当前向量实例(链式调用)
*/
normalize(): this {
const len = this.length;
if (len === 0) {
return this;
}
return this.divide(len);
}
/**
* 获取归一化后的向量(不修改原向量)
* @returns 新的单位向量
*/
normalized(): Vector3 {
return this.clone().normalize();
}
// 几何运算
/**
* 计算到另一个向量的距离
* @param other 另一个向量
* @returns 距离值
*/
distanceTo(other: Vector3): number {
const dx = this.x - other.x;
const dy = this.y - other.y;
const dz = this.z - other.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
/**
* 计算到另一个向量的距离平方
* @param other 另一个向量
* @returns 距离平方值
*/
distanceToSquared(other: Vector3): number {
const dx = this.x - other.x;
const dy = this.y - other.y;
const dz = this.z - other.z;
return dx * dx + dy * dy + dz * dz;
}
/**
* 计算与另一个向量的夹角(弧度)
* @param other 另一个向量
* @returns 夹角0到π
*/
angleTo(other: Vector3): number {
const dot = this.dot(other);
const lenProduct = this.length * other.length;
if (lenProduct === 0) return 0;
return Math.acos(Math.max(-1, Math.min(1, dot / lenProduct)));
}
/**
* 计算向量在另一个向量上的投影
* @param onto 投影目标向量
* @returns 新的投影向量
*/
projectOnto(onto: Vector3): Vector3 {
const dot = this.dot(onto);
const lenSq = onto.lengthSquared;
if (lenSq === 0) return new Vector3();
return onto.clone().multiply(dot / lenSq);
}
// 插值和限制
/**
* 线性插值
* @param target 目标向量
* @param t 插值参数0到1
* @returns 当前向量实例(链式调用)
*/
lerp(target: Vector3, t: number): this {
this.x += (target.x - this.x) * t;
this.y += (target.y - this.y) * t;
this.z += (target.z - this.z) * t;
return this;
}
/**
* 限制向量长度
* @param maxLength 最大长度
* @returns 当前向量实例(链式调用)
*/
clampLength(maxLength: number): this {
const lenSq = this.lengthSquared;
if (lenSq > maxLength * maxLength) {
return this.normalize().multiply(maxLength);
}
return this;
}
// 比较操作
/**
* 检查两个向量是否相等
* @param other 另一个向量
* @param epsilon 容差默认为Number.EPSILON
* @returns 是否相等
*/
equals(other: Vector3, epsilon: number = Number.EPSILON): boolean {
return Math.abs(this.x - other.x) < epsilon &&
Math.abs(this.y - other.y) < epsilon &&
Math.abs(this.z - other.z) < epsilon;
}
/**
* 检查两个向量是否完全相等
* @param other 另一个向量
* @returns 是否完全相等
*/
exactEquals(other: Vector3): boolean {
return this.x === other.x && this.y === other.y && this.z === other.z;
}
// 静态方法
/**
* 向量加法(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的结果向量
*/
static add(a: Vector3, b: Vector3): Vector3 {
return new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
}
/**
* 向量减法(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的结果向量
*/
static subtract(a: Vector3, b: Vector3): Vector3 {
return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
}
/**
* 向量数乘(静态方法)
* @param vector 向量
* @param scalar 标量
* @returns 新的结果向量
*/
static multiply(vector: Vector3, scalar: number): Vector3 {
return new Vector3(vector.x * scalar, vector.y * scalar, vector.z * scalar);
}
/**
* 向量点积(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 点积值
*/
static dot(a: Vector3, b: Vector3): number {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
/**
* 向量叉积(静态方法)
* @param a 向量a
* @param b 向量b
* @returns 新的叉积向量
*/
static cross(a: Vector3, b: Vector3): Vector3 {
return new Vector3(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
);
}
/**
* 计算两点间距离(静态方法)
* @param a 点a
* @param b 点b
* @returns 距离值
*/
static distance(a: Vector3, b: Vector3): number {
const dx = a.x - b.x;
const dy = a.y - b.y;
const dz = a.z - b.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
/**
* 线性插值(静态方法)
* @param a 起始向量
* @param b 目标向量
* @param t 插值参数0到1
* @returns 新的插值结果向量
*/
static lerp(a: Vector3, b: Vector3, t: number): Vector3 {
return new Vector3(
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t,
a.z + (b.z - a.z) * t
);
}
// 字符串转换
/**
* 转换为字符串
* @returns 字符串表示
*/
toString(): string {
return `Vector3(${this.x.toFixed(3)}, ${this.y.toFixed(3)}, ${this.z.toFixed(3)})`;
}
/**
* 转换为数组
* @returns [x, y, z] 数组
*/
toArray(): [number, number, number] {
return [this.x, this.y, this.z];
}
/**
* 转换为普通对象
* @returns {x, y, z} 对象
*/
toObject(): { x: number; y: number; z: number } {
return { x: this.x, y: this.y, z: this.z };
}
}