feat: 添加跨平台运行时、资产系统和UI适配功能 (#256)

* feat(platform-common): 添加WASM加载器和环境检测API

* feat(rapier2d): 新增Rapier2D WASM绑定包

* feat(physics-rapier2d): 添加跨平台WASM加载器

* feat(asset-system): 添加运行时资产目录和bundle格式

* feat(asset-system-editor): 新增编辑器资产管理包

* feat(editor-core): 添加构建系统和模块管理

* feat(editor-app): 重构浏览器预览使用import maps

* feat(platform-web): 添加BrowserRuntime和资产读取

* feat(engine): 添加材质系统和着色器管理

* feat(material): 新增材质系统和着色器编辑器

* feat(tilemap): 增强tilemap编辑器和动画系统

* feat(modules): 添加module.json配置

* feat(core): 添加module.json和类型定义更新

* chore: 更新依赖和构建配置

* refactor(plugins): 更新插件模板使用ModuleManifest

* chore: 添加第三方依赖库

* chore: 移除BehaviourTree-ai和ecs-astar子模块

* docs: 更新README和文档主题样式

* fix: 修复Rust文档测试和添加rapier2d WASM绑定

* fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题

* feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea)

* fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖

* fix: 添加缺失的包依赖修复CI构建

* fix: 修复CodeQL检测到的代码问题

* fix: 修复构建错误和缺失依赖

* fix: 修复类型检查错误

* fix(material-system): 修复tsconfig配置支持TypeScript项目引用

* fix(editor-core): 修复Rollup构建配置添加tauri external

* fix: 修复CodeQL检测到的代码问题

* fix: 修复CodeQL检测到的代码问题
This commit is contained in:
YHH
2025-12-03 22:15:22 +08:00
committed by GitHub
parent caf7622aa0
commit 63f006ab62
496 changed files with 77601 additions and 4067 deletions

21
packages/math/module.json Normal file
View File

@@ -0,0 +1,21 @@
{
"id": "math",
"name": "@esengine/ecs-framework-math",
"displayName": "Math",
"description": "Vector, Matrix, Transform and other math utilities | 向量、矩阵、变换等数学工具",
"version": "1.0.0",
"category": "Core",
"icon": "Calculator",
"tags": ["math", "vector", "matrix", "transform"],
"isCore": true,
"defaultEnabled": true,
"isEngineModule": true,
"canContainContent": false,
"platforms": ["web", "desktop", "mobile"],
"dependencies": [],
"exports": {
"other": ["Vector2", "Vector3", "Matrix3x3", "Matrix4x4", "Quaternion", "Transform", "Rectangle", "Color"]
},
"requiresWasm": false,
"outputPath": "dist/index.mjs"
}

View File

@@ -24,10 +24,10 @@
"clean": "rimraf bin dist tsconfig.tsbuildinfo",
"build:ts": "tsc",
"prebuild": "npm run clean",
"build": "npm run build:ts",
"build": "npm run build:ts && node build-rollup.cjs",
"build:watch": "tsc --watch",
"rebuild": "npm run clean && npm run build",
"build:npm": "npm run build && node build-rollup.cjs",
"build:npm": "npm run build",
"publish:npm": "npm run build:npm && cd dist && npm publish",
"test": "jest --config jest.config.cjs",
"test:watch": "jest --watch --config jest.config.cjs",

556
packages/math/src/Color.ts Normal file
View File

@@ -0,0 +1,556 @@
/**
* Color utility class for game engine
* 游戏引擎颜色工具类
*
* Provides color conversion, manipulation, and packing utilities.
* 提供颜色转换、操作和打包工具。
*/
/**
* RGBA color components
* RGBA 颜色分量
*/
export interface RGBA {
r: number;
g: number;
b: number;
a: number;
}
/**
* HSL color components
* HSL 颜色分量
*/
export interface HSL {
h: number;
s: number;
l: number;
}
/**
* Color class for color manipulation and conversion
* 颜色类,用于颜色操作和转换
*/
export class Color {
/** Red component (0-255) | 红色分量 (0-255) */
public r: number;
/** Green component (0-255) | 绿色分量 (0-255) */
public g: number;
/** Blue component (0-255) | 蓝色分量 (0-255) */
public b: number;
/** Alpha component (0-1) | 透明度分量 (0-1) */
public a: number;
// ===== Predefined Colors | 预定义颜色 =====
/** White (0xFFFFFF) | 白色 */
static readonly WHITE = new Color(255, 255, 255);
/** Black (0x000000) | 黑色 */
static readonly BLACK = new Color(0, 0, 0);
/** Red (0xFF0000) | 红色 */
static readonly RED = new Color(255, 0, 0);
/** Green (0x00FF00) | 绿色 */
static readonly GREEN = new Color(0, 255, 0);
/** Blue (0x0000FF) | 蓝色 */
static readonly BLUE = new Color(0, 0, 255);
/** Yellow (0xFFFF00) | 黄色 */
static readonly YELLOW = new Color(255, 255, 0);
/** Cyan (0x00FFFF) | 青色 */
static readonly CYAN = new Color(0, 255, 255);
/** Magenta (0xFF00FF) | 品红色 */
static readonly MAGENTA = new Color(255, 0, 255);
/** Transparent (0x00000000) | 透明 */
static readonly TRANSPARENT = new Color(0, 0, 0, 0);
/** Gray (0x808080) | 灰色 */
static readonly GRAY = new Color(128, 128, 128);
/**
* Create a new Color instance
* 创建新的 Color 实例
*/
constructor(r: number = 255, g: number = 255, b: number = 255, a: number = 1) {
this.r = Math.round(Math.max(0, Math.min(255, r)));
this.g = Math.round(Math.max(0, Math.min(255, g)));
this.b = Math.round(Math.max(0, Math.min(255, b)));
this.a = Math.max(0, Math.min(1, a));
}
// ===== Factory Methods | 工厂方法 =====
/**
* Create color from hex string
* 从十六进制字符串创建颜色
* @param hex Hex string (e.g., "#FF0000", "#F00", "FF0000") | 十六进制字符串
* @param alpha Optional alpha value (0-1) | 可选的透明度值
*/
static fromHex(hex: string, alpha: number = 1): Color {
const { r, g, b } = Color.hexToRgb(hex);
return new Color(r, g, b, alpha);
}
/**
* Create color from packed uint32 (0xRRGGBB or 0xAARRGGBB)
* 从打包的 uint32 创建颜色
* @param value Packed color value | 打包的颜色值
* @param hasAlpha Whether value includes alpha | 是否包含透明度
*/
static fromUint32(value: number, hasAlpha: boolean = false): Color {
if (hasAlpha) {
const a = ((value >> 24) & 0xFF) / 255;
const r = (value >> 16) & 0xFF;
const g = (value >> 8) & 0xFF;
const b = value & 0xFF;
return new Color(r, g, b, a);
} else {
const r = (value >> 16) & 0xFF;
const g = (value >> 8) & 0xFF;
const b = value & 0xFF;
return new Color(r, g, b);
}
}
/**
* Create color from HSL values
* 从 HSL 值创建颜色
* @param h Hue (0-360) | 色相
* @param s Saturation (0-1) | 饱和度
* @param l Lightness (0-1) | 亮度
* @param a Alpha (0-1) | 透明度
*/
static fromHSL(h: number, s: number, l: number, a: number = 1): Color {
const { r, g, b } = Color.hslToRgb(h, s, l);
return new Color(r, g, b, a);
}
/**
* Create color from normalized float values (0-1)
* 从归一化浮点值创建颜色 (0-1)
*/
static fromFloat(r: number, g: number, b: number, a: number = 1): Color {
return new Color(r * 255, g * 255, b * 255, a);
}
// ===== Conversion Methods | 转换方法 =====
/**
* Convert hex string to RGB
* 将十六进制字符串转换为 RGB
*/
static hexToRgb(hex: string): { r: number; g: number; b: number } {
const colorHex = hex.replace('#', '');
let r = 255, g = 255, b = 255;
if (colorHex.length === 6) {
r = parseInt(colorHex.substring(0, 2), 16);
g = parseInt(colorHex.substring(2, 4), 16);
b = parseInt(colorHex.substring(4, 6), 16);
} else if (colorHex.length === 3) {
r = parseInt(colorHex[0] + colorHex[0], 16);
g = parseInt(colorHex[1] + colorHex[1], 16);
b = parseInt(colorHex[2] + colorHex[2], 16);
} else if (colorHex.length === 8) {
// AARRGGBB format
r = parseInt(colorHex.substring(2, 4), 16);
g = parseInt(colorHex.substring(4, 6), 16);
b = parseInt(colorHex.substring(6, 8), 16);
}
return { r, g, b };
}
/**
* Convert RGB to hex string
* 将 RGB 转换为十六进制字符串
*/
static rgbToHex(r: number, g: number, b: number): string {
const toHex = (n: number) => Math.round(n).toString(16).padStart(2, '0');
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
/**
* Convert HSL to RGB
* 将 HSL 转换为 RGB
*/
static hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {
h = ((h % 360) + 360) % 360 / 360;
s = Math.max(0, Math.min(1, s));
l = Math.max(0, Math.min(1, l));
let r: number, g: number, b: number;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p: number, q: number, t: number) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255)
};
}
/**
* Convert RGB to HSL
* 将 RGB 转换为 HSL
*/
static rgbToHsl(r: number, g: number, b: number): HSL {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0;
let s = 0;
const l = (max + min) / 2;
if (max !== min) {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
break;
case g:
h = ((b - r) / d + 2) / 6;
break;
case b:
h = ((r - g) / d + 4) / 6;
break;
}
}
return { h: h * 360, s, l };
}
// ===== Packing Methods | 打包方法 =====
/**
* Pack color to uint32 (0xRRGGBB)
* 打包颜色为 uint32 (0xRRGGBB)
*/
static packRGB(r: number, g: number, b: number): number {
return ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
/**
* Pack color to uint32 (0xAARRGGBB)
* 打包颜色为 uint32 (0xAARRGGBB)
*/
static packARGB(r: number, g: number, b: number, a: number): number {
const alpha = Math.round(a * 255);
return ((alpha & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
/**
* Pack color to uint32 for WebGL (0xAABBGGRR)
* 打包颜色为 WebGL 格式的 uint32 (0xAABBGGRR)
*/
static packABGR(r: number, g: number, b: number, a: number): number {
const alpha = Math.round(a * 255);
return ((alpha & 0xFF) << 24) | ((b & 0xFF) << 16) | ((g & 0xFF) << 8) | (r & 0xFF);
}
/**
* Pack hex string and alpha to WebGL uint32 (0xAABBGGRR)
* 将十六进制字符串和透明度打包为 WebGL 格式 uint32
*/
static packHexAlpha(hex: string, alpha: number): number {
const { r, g, b } = Color.hexToRgb(hex);
return Color.packABGR(r, g, b, alpha);
}
/**
* Unpack uint32 to RGBA (assumes 0xAARRGGBB)
* 解包 uint32 为 RGBA (假设格式为 0xAARRGGBB)
*/
static unpackARGB(value: number): RGBA {
return {
a: ((value >> 24) & 0xFF) / 255,
r: (value >> 16) & 0xFF,
g: (value >> 8) & 0xFF,
b: value & 0xFF
};
}
/**
* Unpack uint32 to RGBA (assumes 0xAABBGGRR - WebGL format)
* 解包 uint32 为 RGBA (假设格式为 0xAABBGGRR - WebGL 格式)
*/
static unpackABGR(value: number): RGBA {
return {
a: ((value >> 24) & 0xFF) / 255,
b: (value >> 16) & 0xFF,
g: (value >> 8) & 0xFF,
r: value & 0xFF
};
}
// ===== Color Operations | 颜色操作 =====
/**
* Interpolate between two colors
* 在两个颜色之间插值
*/
static lerp(from: Color, to: Color, t: number): Color {
t = Math.max(0, Math.min(1, t));
return new Color(
from.r + (to.r - from.r) * t,
from.g + (to.g - from.g) * t,
from.b + (to.b - from.b) * t,
from.a + (to.a - from.a) * t
);
}
/**
* Interpolate between two packed uint32 colors (0xRRGGBB)
* 在两个打包的 uint32 颜色之间插值
*/
static lerpUint32(from: number, to: number, t: number): number {
t = Math.max(0, Math.min(1, t));
const fromR = (from >> 16) & 0xFF;
const fromG = (from >> 8) & 0xFF;
const fromB = from & 0xFF;
const toR = (to >> 16) & 0xFF;
const toG = (to >> 8) & 0xFF;
const toB = to & 0xFF;
const r = Math.round(fromR + (toR - fromR) * t);
const g = Math.round(fromG + (toG - fromG) * t);
const b = Math.round(fromB + (toB - fromB) * t);
return (r << 16) | (g << 8) | b;
}
/**
* Mix two colors
* 混合两个颜色
*/
static mix(color1: Color, color2: Color, ratio: number = 0.5): Color {
return Color.lerp(color1, color2, ratio);
}
/**
* Lighten a color
* 使颜色变亮
*/
static lighten(color: Color, amount: number): Color {
const hsl = Color.rgbToHsl(color.r, color.g, color.b);
hsl.l = Math.min(1, hsl.l + amount);
const rgb = Color.hslToRgb(hsl.h, hsl.s, hsl.l);
return new Color(rgb.r, rgb.g, rgb.b, color.a);
}
/**
* Darken a color
* 使颜色变暗
*/
static darken(color: Color, amount: number): Color {
const hsl = Color.rgbToHsl(color.r, color.g, color.b);
hsl.l = Math.max(0, hsl.l - amount);
const rgb = Color.hslToRgb(hsl.h, hsl.s, hsl.l);
return new Color(rgb.r, rgb.g, rgb.b, color.a);
}
/**
* Saturate a color
* 增加颜色饱和度
*/
static saturate(color: Color, amount: number): Color {
const hsl = Color.rgbToHsl(color.r, color.g, color.b);
hsl.s = Math.min(1, hsl.s + amount);
const rgb = Color.hslToRgb(hsl.h, hsl.s, hsl.l);
return new Color(rgb.r, rgb.g, rgb.b, color.a);
}
/**
* Desaturate a color
* 降低颜色饱和度
*/
static desaturate(color: Color, amount: number): Color {
const hsl = Color.rgbToHsl(color.r, color.g, color.b);
hsl.s = Math.max(0, hsl.s - amount);
const rgb = Color.hslToRgb(hsl.h, hsl.s, hsl.l);
return new Color(rgb.r, rgb.g, rgb.b, color.a);
}
/**
* Invert a color
* 反转颜色
*/
static invert(color: Color): Color {
return new Color(255 - color.r, 255 - color.g, 255 - color.b, color.a);
}
/**
* Convert color to grayscale
* 将颜色转换为灰度
*/
static grayscale(color: Color): Color {
const gray = Math.round(0.299 * color.r + 0.587 * color.g + 0.114 * color.b);
return new Color(gray, gray, gray, color.a);
}
/**
* Get color luminance (perceived brightness)
* 获取颜色亮度(感知亮度)
*/
static luminance(color: Color): number {
return (0.299 * color.r + 0.587 * color.g + 0.114 * color.b) / 255;
}
/**
* Get contrast ratio between two colors
* 获取两个颜色之间的对比度
*/
static contrastRatio(color1: Color, color2: Color): number {
const l1 = Color.luminance(color1);
const l2 = Color.luminance(color2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
// ===== Instance Methods | 实例方法 =====
/**
* Convert to hex string
* 转换为十六进制字符串
*/
toHex(): string {
return Color.rgbToHex(this.r, this.g, this.b);
}
/**
* Convert to hex string with alpha
* 转换为带透明度的十六进制字符串
*/
toHexAlpha(): string {
const alphaHex = Math.round(this.a * 255).toString(16).padStart(2, '0');
return `#${alphaHex}${this.toHex().slice(1)}`;
}
/**
* Convert to CSS rgba string
* 转换为 CSS rgba 字符串
*/
toRgba(): string {
return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`;
}
/**
* Convert to CSS rgb string
* 转换为 CSS rgb 字符串
*/
toRgb(): string {
return `rgb(${this.r}, ${this.g}, ${this.b})`;
}
/**
* Convert to HSL
* 转换为 HSL
*/
toHSL(): HSL {
return Color.rgbToHsl(this.r, this.g, this.b);
}
/**
* Pack to uint32 (0xRRGGBB)
* 打包为 uint32 (0xRRGGBB)
*/
toUint32(): number {
return Color.packRGB(this.r, this.g, this.b);
}
/**
* Pack to uint32 with alpha (0xAARRGGBB)
* 打包为带透明度的 uint32 (0xAARRGGBB)
*/
toUint32Alpha(): number {
return Color.packARGB(this.r, this.g, this.b, this.a);
}
/**
* Pack to WebGL uint32 (0xAABBGGRR)
* 打包为 WebGL 格式 uint32 (0xAABBGGRR)
*/
toWebGL(): number {
return Color.packABGR(this.r, this.g, this.b, this.a);
}
/**
* Get normalized float array [r, g, b, a] (0-1)
* 获取归一化浮点数组 [r, g, b, a] (0-1)
*/
toFloatArray(): [number, number, number, number] {
return [this.r / 255, this.g / 255, this.b / 255, this.a];
}
/**
* Clone this color
* 克隆此颜色
*/
clone(): Color {
return new Color(this.r, this.g, this.b, this.a);
}
/**
* Set color values
* 设置颜色值
*/
set(r: number, g: number, b: number, a?: number): this {
this.r = Math.round(Math.max(0, Math.min(255, r)));
this.g = Math.round(Math.max(0, Math.min(255, g)));
this.b = Math.round(Math.max(0, Math.min(255, b)));
if (a !== undefined) {
this.a = Math.max(0, Math.min(1, a));
}
return this;
}
/**
* Copy from another color
* 从另一个颜色复制
*/
copy(other: Color): this {
this.r = other.r;
this.g = other.g;
this.b = other.b;
this.a = other.a;
return this;
}
/**
* Check equality with another color
* 检查与另一个颜色是否相等
*/
equals(other: Color): boolean {
return this.r === other.r && this.g === other.g && this.b === other.b && this.a === other.a;
}
/**
* String representation
* 字符串表示
*/
toString(): string {
return `Color(${this.r}, ${this.g}, ${this.b}, ${this.a})`;
}
}

View File

@@ -6,6 +6,7 @@
* - 碰撞检测算法
* - 动画插值和缓动函数
* - 数学工具函数
* - 颜色工具类
*/
// 核心数学类
@@ -18,6 +19,9 @@ export { Circle } from './Circle';
// 数学工具
export { MathUtils } from './MathUtils';
// 颜色工具
export { Color, type RGBA, type HSL } from './Color';
// 碰撞检测
export * from './Collision';