diff --git a/.changeset/math-blueprint-nodes.md b/.changeset/math-blueprint-nodes.md new file mode 100644 index 00000000..67aace38 --- /dev/null +++ b/.changeset/math-blueprint-nodes.md @@ -0,0 +1,11 @@ +--- +"@esengine/ecs-framework-math": minor +--- + +feat(math): add blueprint nodes for math library + +- Add Vector2 blueprint nodes (Make, Break, Add, Sub, Mul, Length, Normalize, Dot, Cross, Distance, Lerp, Rotate, FromAngle) +- Add Fixed32 blueprint nodes (FromFloat, FromInt, ToFloat, ToInt, arithmetic operations, Abs, Sqrt, Floor, Ceil, Round, Sign, Min, Max, Clamp, Lerp) +- Add FixedVector2 blueprint nodes (Make, Break, Add, Sub, Mul, Negate, Length, Normalize, Dot, Cross, Distance, Lerp) +- Add Color blueprint nodes (Make, Break, FromHex, ToHex, FromHSL, ToHSL, Lerp, Lighten, Darken, Saturate, Desaturate, Invert, Grayscale, Luminance, constants) +- Add documentation for math blueprint nodes (Chinese and English) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bea823f1..85867a7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,9 +63,9 @@ jobs: - name: Build framework packages run: | pnpm --filter @esengine/ecs-framework build + pnpm --filter @esengine/blueprint build pnpm --filter @esengine/ecs-framework-math build pnpm --filter @esengine/behavior-tree build - pnpm --filter @esengine/blueprint build pnpm --filter @esengine/fsm build pnpm --filter @esengine/timer build pnpm --filter @esengine/spatial build diff --git a/.github/workflows/release-changesets.yml b/.github/workflows/release-changesets.yml index a668135e..16743361 100644 --- a/.github/workflows/release-changesets.yml +++ b/.github/workflows/release-changesets.yml @@ -45,9 +45,9 @@ jobs: run: | # Only build packages managed by Changesets (not in ignore list) pnpm --filter "@esengine/ecs-framework" build + pnpm --filter "@esengine/blueprint" build pnpm --filter "@esengine/ecs-framework-math" build pnpm --filter "@esengine/behavior-tree" build - pnpm --filter "@esengine/blueprint" build pnpm --filter "@esengine/fsm" build pnpm --filter "@esengine/timer" build pnpm --filter "@esengine/spatial" build diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index afc6232d..320b1b64 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -247,6 +247,14 @@ export default defineConfig({ { label: '实际示例', slug: 'modules/blueprint/examples', translations: { en: 'Examples' } }, ], }, + { + label: '数学库', + translations: { en: 'Math' }, + items: [ + { label: '概述', slug: 'modules/math', translations: { en: 'Overview' } }, + { label: '蓝图节点', slug: 'modules/math/blueprint-nodes', translations: { en: 'Blueprint Nodes' } }, + ], + }, { label: '程序生成', translations: { en: 'Procgen' }, diff --git a/docs/src/content/docs/en/modules/blueprint/nodes.md b/docs/src/content/docs/en/modules/blueprint/nodes.md index 5b5ad9c7..9637ba37 100644 --- a/docs/src/content/docs/en/modules/blueprint/nodes.md +++ b/docs/src/content/docs/en/modules/blueprint/nodes.md @@ -472,6 +472,12 @@ Control blueprint execution flow: | `Less` | A < B | Boolean | | `Less Or Equal` | A <= B | Boolean | +### Extended Math Nodes + +> **Vector2, Fixed32, FixedVector2, Color** and other advanced math nodes are provided by the `@esengine/ecs-framework-math` module. +> +> See: [Math Blueprint Nodes](/en/modules/math/blueprint-nodes) + ### Example: Clamp Value
@@ -604,6 +610,7 @@ Blueprint-defined variables automatically generate Get and Set nodes: ## Related Documentation +- [Math Blueprint Nodes](/en/modules/math/blueprint-nodes) - Vector2, Fixed32, Color and other math nodes - [Blueprint Editor Guide](/en/modules/blueprint/editor-guide) - Learn how to use the editor - [Custom Nodes](/en/modules/blueprint/custom-nodes) - Create custom nodes - [Blueprint VM](/en/modules/blueprint/vm) - Runtime API diff --git a/docs/src/content/docs/en/modules/math/blueprint-nodes.md b/docs/src/content/docs/en/modules/math/blueprint-nodes.md new file mode 100644 index 00000000..54f4bd58 --- /dev/null +++ b/docs/src/content/docs/en/modules/math/blueprint-nodes.md @@ -0,0 +1,489 @@ +--- +title: "Math Blueprint Nodes" +description: "Blueprint nodes provided by the Math module - Vector2, Fixed32, FixedVector2, Color" +--- + +This document describes the blueprint nodes provided by the `@esengine/ecs-framework-math` module. + +> **Note**: These nodes require the math module to be installed. + + + +## Pin Type Legend + +
+
Float
+
Vector2
+
Fixed32
+
FixedVector2
+
Color
+
+ +--- + +## Vector2 Nodes + +2D vector operations for position, velocity, and direction calculations. + +### Node List + +| Node | Description | Inputs | Output | +|------|-------------|--------|--------| +| `Make Vector2` | Create Vector2 from X, Y | X, Y | Vector2 | +| `Break Vector2` | Decompose Vector2 to X, Y | Vector | X, Y | +| `Vector2 +` | Vector addition | A, B | Vector2 | +| `Vector2 -` | Vector subtraction | A, B | Vector2 | +| `Vector2 *` | Vector scaling | Vector, Scalar | Vector2 | +| `Vector2 Length` | Get vector length | Vector | Float | +| `Vector2 Normalize` | Normalize to unit vector | Vector | Vector2 | +| `Vector2 Dot` | Dot product | A, B | Float | +| `Vector2 Cross` | 2D cross product | A, B | Float | +| `Vector2 Distance` | Distance between two points | A, B | Float | +| `Vector2 Lerp` | Linear interpolation | A, B, T | Vector2 | +| `Vector2 Rotate` | Rotate by angle (radians) | Vector, Angle | Vector2 | +| `Vector2 From Angle` | Create unit vector from angle | Angle | Vector2 | + +### Example: Calculate Movement Direction + +Direction vector from start to end point: + +
+ +
+
Make Vector2
+
+
+ + X + 0 +
+
+ + Y + 0 +
+
+ + Vector +
+
+
+
+
Make Vector2
+
+
+ + X + 100 +
+
+ + Y + 50 +
+
+ + Vector +
+
+
+
+
Vector2 -
+
+
+ + A +
+
+ + B +
+
+ + Result +
+
+
+
+
Vector2 Normalize
+
+
+ + Vector +
+
+ + Result +
+
+
+
+ +### Example: Circular Motion + +Calculate circular position using angle and radius: + +
+ +
+
Vector2 From Angle
+
+
+ + Angle + 1.57 +
+
+ + Vector +
+
+
+
+
Vector2 *
+
+
+ + Vector +
+
+ + Scalar + 50 +
+
+ + Result +
+
+
+
+
Vector2 +
+
+
+ + A (Center) +
+
+ + B +
+
+ + Position +
+
+
+
+ +--- + +## Fixed32 Nodes (Fixed-Point Numbers) + +Q16.16 fixed-point number operations for lockstep networking games, ensuring cross-platform calculation consistency. + +### Node List + +| Node | Description | Inputs | Output | +|------|-------------|--------|--------| +| `Fixed32 From Float` | Create from float | Float | Fixed32 | +| `Fixed32 From Int` | Create from integer | Int | Fixed32 | +| `Fixed32 To Float` | Convert to float | Fixed32 | Float | +| `Fixed32 To Int` | Convert to integer | Fixed32 | Int | +| `Fixed32 +` | Addition | A, B | Fixed32 | +| `Fixed32 -` | Subtraction | A, B | Fixed32 | +| `Fixed32 *` | Multiplication | A, B | Fixed32 | +| `Fixed32 /` | Division | A, B | Fixed32 | +| `Fixed32 Abs` | Absolute value | Value | Fixed32 | +| `Fixed32 Sqrt` | Square root | Value | Fixed32 | +| `Fixed32 Floor` | Floor | Value | Fixed32 | +| `Fixed32 Ceil` | Ceiling | Value | Fixed32 | +| `Fixed32 Round` | Round | Value | Fixed32 | +| `Fixed32 Sign` | Sign (-1, 0, 1) | Value | Fixed32 | +| `Fixed32 Min` | Minimum | A, B | Fixed32 | +| `Fixed32 Max` | Maximum | A, B | Fixed32 | +| `Fixed32 Clamp` | Clamp to range | Value, Min, Max | Fixed32 | +| `Fixed32 Lerp` | Linear interpolation | A, B, T | Fixed32 | + +### Example: Lockstep Movement Speed Calculation + +
+ +
+
Fixed32 From Float
+
+
+ + Value + 5.0 +
+
+ + Speed +
+
+
+
+
Fixed32 From Float
+
+
+ + Value + 0.016 +
+
+ + DeltaTime +
+
+
+
+
Fixed32 *
+
+
+ + A +
+
+ + B +
+
+ + Result +
+
+
+
+
Fixed32 To Float
+
+
+ + Fixed +
+
+ + Float +
+
+
+
+ +--- + +## FixedVector2 Nodes (Fixed-Point Vectors) + +Fixed-point vector operations for deterministic physics calculations, suitable for lockstep networking. + +### Node List + +| Node | Description | Inputs | Output | +|------|-------------|--------|--------| +| `Make FixedVector2` | Create from X, Y floats | X, Y | FixedVector2 | +| `Break FixedVector2` | Decompose to X, Y floats | Vector | X, Y | +| `FixedVector2 +` | Vector addition | A, B | FixedVector2 | +| `FixedVector2 -` | Vector subtraction | A, B | FixedVector2 | +| `FixedVector2 *` | Scale by Fixed32 | Vector, Scalar | FixedVector2 | +| `FixedVector2 Negate` | Negate vector | Vector | FixedVector2 | +| `FixedVector2 Length` | Get length | Vector | Fixed32 | +| `FixedVector2 Normalize` | Normalize | Vector | FixedVector2 | +| `FixedVector2 Dot` | Dot product | A, B | Fixed32 | +| `FixedVector2 Cross` | 2D cross product | A, B | Fixed32 | +| `FixedVector2 Distance` | Distance between points | A, B | Fixed32 | +| `FixedVector2 Lerp` | Linear interpolation | A, B, T | FixedVector2 | + +### Example: Deterministic Position Update + +
+ +
+
Make FixedVector2
+
+
+ + X + 10 +
+
+ + Y + 20 +
+
+ + Position +
+
+
+
+
Make FixedVector2
+
+
+ + X + 1 +
+
+ + Y + 0 +
+
+ + Velocity +
+
+
+
+
FixedVector2 +
+
+
+ + A +
+
+ + B +
+
+ + New Position +
+
+
+
+ +--- + +## Color Nodes + +Color creation and manipulation nodes. + +### Node List + +| Node | Description | Inputs | Output | +|------|-------------|--------|--------| +| `Make Color` | Create from RGBA | R, G, B, A | Color | +| `Break Color` | Decompose to RGBA | Color | R, G, B, A | +| `Color From Hex` | Create from hex string | Hex | Color | +| `Color To Hex` | Convert to hex string | Color | String | +| `Color From HSL` | Create from HSL | H, S, L | Color | +| `Color To HSL` | Convert to HSL | Color | H, S, L | +| `Color Lerp` | Color interpolation | A, B, T | Color | +| `Color Lighten` | Lighten | Color, Amount | Color | +| `Color Darken` | Darken | Color, Amount | Color | +| `Color Saturate` | Increase saturation | Color, Amount | Color | +| `Color Desaturate` | Decrease saturation | Color, Amount | Color | +| `Color Invert` | Invert | Color | Color | +| `Color Grayscale` | Convert to grayscale | Color | Color | +| `Color Luminance` | Get luminance | Color | Float | + +### Color Constants + +| Node | Value | +|------|-------| +| `Color White` | (1, 1, 1, 1) | +| `Color Black` | (0, 0, 0, 1) | +| `Color Red` | (1, 0, 0, 1) | +| `Color Green` | (0, 1, 0, 1) | +| `Color Blue` | (0, 0, 1, 1) | +| `Color Transparent` | (0, 0, 0, 0) | + +### Example: Color Transition Animation + +
+ +
+
Color Red
+
+
+ + Color +
+
+
+
+
Color Blue
+
+
+ + Color +
+
+
+
+
Color Lerp
+
+
+ + A +
+
+ + B +
+
+ + T + 0.5 +
+
+ + Result +
+
+
+
+ +### Example: Create Color from Hex + +
+ +
+
Color From Hex
+
+
+ + Hex + "#FF5722" +
+
+ + Color +
+
+
+
+
Break Color
+
+
+ + Color +
+
+ + R +
+
+ + G +
+
+ + B +
+
+ + A +
+
+
+
+ +--- + +## Related Documentation + +- [Blueprint Node Reference](/en/modules/blueprint/nodes) - Core blueprint nodes +- [Blueprint Editor Guide](/en/modules/blueprint/editor-guide) - Editor usage +- [Custom Nodes](/en/modules/blueprint/custom-nodes) - Create custom nodes diff --git a/docs/src/content/docs/en/modules/math/index.md b/docs/src/content/docs/en/modules/math/index.md new file mode 100644 index 00000000..a27bccd4 --- /dev/null +++ b/docs/src/content/docs/en/modules/math/index.md @@ -0,0 +1,79 @@ +--- +title: "Math Library" +description: "ESEngine Math Library - Vector2, Fixed32, FixedVector2, Color and other math types" +--- + +The `@esengine/ecs-framework-math` module provides common math types and operations for game development. + +## Core Types + +| Type | Description | +|------|-------------| +| `Vector2` | 2D floating-point vector for position, velocity, direction | +| `Fixed32` | Q16.16 fixed-point number for deterministic lockstep calculations | +| `FixedVector2` | 2D fixed-point vector for deterministic physics | +| `Color` | RGBA color | + +## Features + +### Vector2 + +- Addition, subtraction, scaling +- Dot product, cross product +- Length, normalization +- Distance, interpolation +- Rotation, angle conversion + +### Fixed32 Fixed-Point Numbers + +Designed for lockstep networking games, ensuring cross-platform calculation consistency: + +- Basic operations: add, subtract, multiply, divide +- Math functions: absolute value, square root, rounding +- Comparison, clamping, interpolation +- Constants: 0, 1, 0.5, PI, 2*PI + +### Color + +- RGB/RGBA creation and decomposition +- Hex string conversion +- HSL color space conversion +- Color operations: lighten, darken, saturation adjustment +- Color blending and interpolation + +## Blueprint Support + +The math library provides rich blueprint nodes, see: + +- [Math Blueprint Nodes](/en/modules/math/blueprint-nodes) + +## Installation + +```bash +pnpm add @esengine/ecs-framework-math +``` + +## Basic Usage + +```typescript +import { Vector2, Fixed32, FixedVector2, Color } from '@esengine/ecs-framework-math'; + +// Vector2 +const pos = new Vector2(10, 20); +const dir = pos.normalized(); + +// Fixed32 (lockstep) +const speed = Fixed32.from(5.0); +const dt = Fixed32.from(0.016); +const distance = speed.mul(dt); + +// FixedVector2 +const fixedPos = FixedVector2.from(10, 20); +const fixedVel = FixedVector2.from(1, 0); +const newPos = fixedPos.add(fixedVel); + +// Color +const red = Color.RED; +const blue = Color.BLUE; +const purple = Color.lerp(red, blue, 0.5); +``` diff --git a/docs/src/content/docs/modules/blueprint/nodes.md b/docs/src/content/docs/modules/blueprint/nodes.md index fa70af3b..85785101 100644 --- a/docs/src/content/docs/modules/blueprint/nodes.md +++ b/docs/src/content/docs/modules/blueprint/nodes.md @@ -470,6 +470,12 @@ description: "蓝图内置 ECS 操作节点完整参考" | `Less` | A < B | Boolean | | `Less Or Equal` | A <= B | Boolean | +### 扩展数学节点 + +> **Vector2、Fixed32、FixedVector2、Color** 等高级数学节点由 `@esengine/ecs-framework-math` 模块提供。 +> +> 详见:[数学库蓝图节点](/modules/math/blueprint-nodes) + ### 示例:钳制数值
@@ -535,6 +541,7 @@ description: "蓝图内置 ECS 操作节点完整参考" ## 相关文档 +- [数学库蓝图节点](/modules/math/blueprint-nodes) - Vector2、Fixed32、Color 等数学节点 - [蓝图编辑器指南](/modules/blueprint/editor-guide) - 学习如何使用编辑器 - [自定义节点](/modules/blueprint/custom-nodes) - 创建自定义节点 - [蓝图虚拟机](/modules/blueprint/vm) - 运行时 API diff --git a/docs/src/content/docs/modules/math/blueprint-nodes.md b/docs/src/content/docs/modules/math/blueprint-nodes.md new file mode 100644 index 00000000..5b2d7064 --- /dev/null +++ b/docs/src/content/docs/modules/math/blueprint-nodes.md @@ -0,0 +1,489 @@ +--- +title: "数学库蓝图节点" +description: "Math 模块提供的蓝图节点 - Vector2、Fixed32、FixedVector2、Color" +--- + +本文档介绍 `@esengine/ecs-framework-math` 模块提供的蓝图节点。 + +> **注意**:这些节点需要安装 math 模块才能使用。 + + + +## 引脚类型说明 + +
+
浮点数 (Float)
+
Vector2
+
Fixed32
+
FixedVector2
+
Color
+
+ +--- + +## Vector2 节点 + +2D 向量操作,用于位置、速度、方向计算。 + +### 节点列表 + +| 节点 | 说明 | 输入 | 输出 | +|------|------|------|------| +| `Make Vector2` | 从 X, Y 创建 Vector2 | X, Y | Vector2 | +| `Break Vector2` | 分解 Vector2 为 X, Y | Vector | X, Y | +| `Vector2 +` | 向量加法 | A, B | Vector2 | +| `Vector2 -` | 向量减法 | A, B | Vector2 | +| `Vector2 *` | 向量缩放 | Vector, Scalar | Vector2 | +| `Vector2 Length` | 获取向量长度 | Vector | Float | +| `Vector2 Normalize` | 归一化为单位向量 | Vector | Vector2 | +| `Vector2 Dot` | 点积 | A, B | Float | +| `Vector2 Cross` | 2D 叉积 | A, B | Float | +| `Vector2 Distance` | 两点距离 | A, B | Float | +| `Vector2 Lerp` | 线性插值 | A, B, T | Vector2 | +| `Vector2 Rotate` | 旋转(弧度) | Vector, Angle | Vector2 | +| `Vector2 From Angle` | 从角度创建单位向量 | Angle | Vector2 | + +### 示例:计算移动方向 + +从起点到终点的方向向量: + +
+ +
+
Make Vector2
+
+
+ + X + 0 +
+
+ + Y + 0 +
+
+ + Vector +
+
+
+
+
Make Vector2
+
+
+ + X + 100 +
+
+ + Y + 50 +
+
+ + Vector +
+
+
+
+
Vector2 -
+
+
+ + A +
+
+ + B +
+
+ + Result +
+
+
+
+
Vector2 Normalize
+
+
+ + Vector +
+
+ + Result +
+
+
+
+ +### 示例:圆周运动 + +使用角度和半径计算圆周位置: + +
+ +
+
Vector2 From Angle
+
+
+ + Angle + 1.57 +
+
+ + Vector +
+
+
+
+
Vector2 *
+
+
+ + Vector +
+
+ + Scalar + 50 +
+
+ + Result +
+
+
+
+
Vector2 +
+
+
+ + A (Center) +
+
+ + B +
+
+ + Position +
+
+
+
+ +--- + +## Fixed32 定点数节点 + +Q16.16 定点数运算,适用于帧同步网络游戏,保证跨平台计算一致性。 + +### 节点列表 + +| 节点 | 说明 | 输入 | 输出 | +|------|------|------|------| +| `Fixed32 From Float` | 从浮点数创建 | Float | Fixed32 | +| `Fixed32 From Int` | 从整数创建 | Int | Fixed32 | +| `Fixed32 To Float` | 转换为浮点数 | Fixed32 | Float | +| `Fixed32 To Int` | 转换为整数 | Fixed32 | Int | +| `Fixed32 +` | 加法 | A, B | Fixed32 | +| `Fixed32 -` | 减法 | A, B | Fixed32 | +| `Fixed32 *` | 乘法 | A, B | Fixed32 | +| `Fixed32 /` | 除法 | A, B | Fixed32 | +| `Fixed32 Abs` | 绝对值 | Value | Fixed32 | +| `Fixed32 Sqrt` | 平方根 | Value | Fixed32 | +| `Fixed32 Floor` | 向下取整 | Value | Fixed32 | +| `Fixed32 Ceil` | 向上取整 | Value | Fixed32 | +| `Fixed32 Round` | 四舍五入 | Value | Fixed32 | +| `Fixed32 Sign` | 符号 (-1, 0, 1) | Value | Fixed32 | +| `Fixed32 Min` | 最小值 | A, B | Fixed32 | +| `Fixed32 Max` | 最大值 | A, B | Fixed32 | +| `Fixed32 Clamp` | 钳制范围 | Value, Min, Max | Fixed32 | +| `Fixed32 Lerp` | 线性插值 | A, B, T | Fixed32 | + +### 示例:帧同步移动速度计算 + +
+ +
+
Fixed32 From Float
+
+
+ + Value + 5.0 +
+
+ + Speed +
+
+
+
+
Fixed32 From Float
+
+
+ + Value + 0.016 +
+
+ + DeltaTime +
+
+
+
+
Fixed32 *
+
+
+ + A +
+
+ + B +
+
+ + Result +
+
+
+
+
Fixed32 To Float
+
+
+ + Fixed +
+
+ + Float +
+
+
+
+ +--- + +## FixedVector2 定点向量节点 + +定点向量运算,用于确定性物理计算,适用于帧同步。 + +### 节点列表 + +| 节点 | 说明 | 输入 | 输出 | +|------|------|------|------| +| `Make FixedVector2` | 从 X, Y 浮点数创建 | X, Y | FixedVector2 | +| `Break FixedVector2` | 分解为 X, Y 浮点数 | Vector | X, Y | +| `FixedVector2 +` | 向量加法 | A, B | FixedVector2 | +| `FixedVector2 -` | 向量减法 | A, B | FixedVector2 | +| `FixedVector2 *` | 按 Fixed32 缩放 | Vector, Scalar | FixedVector2 | +| `FixedVector2 Negate` | 取反 | Vector | FixedVector2 | +| `FixedVector2 Length` | 获取长度 | Vector | Fixed32 | +| `FixedVector2 Normalize` | 归一化 | Vector | FixedVector2 | +| `FixedVector2 Dot` | 点积 | A, B | Fixed32 | +| `FixedVector2 Cross` | 2D 叉积 | A, B | Fixed32 | +| `FixedVector2 Distance` | 两点距离 | A, B | Fixed32 | +| `FixedVector2 Lerp` | 线性插值 | A, B, T | FixedVector2 | + +### 示例:确定性位置更新 + +
+ +
+
Make FixedVector2
+
+
+ + X + 10 +
+
+ + Y + 20 +
+
+ + Position +
+
+
+
+
Make FixedVector2
+
+
+ + X + 1 +
+
+ + Y + 0 +
+
+ + Velocity +
+
+
+
+
FixedVector2 +
+
+
+ + A +
+
+ + B +
+
+ + New Position +
+
+
+
+ +--- + +## Color 颜色节点 + +颜色创建与操作节点。 + +### 节点列表 + +| 节点 | 说明 | 输入 | 输出 | +|------|------|------|------| +| `Make Color` | 从 RGBA 创建 | R, G, B, A | Color | +| `Break Color` | 分解为 RGBA | Color | R, G, B, A | +| `Color From Hex` | 从十六进制字符串创建 | Hex | Color | +| `Color To Hex` | 转换为十六进制字符串 | Color | String | +| `Color From HSL` | 从 HSL 创建 | H, S, L | Color | +| `Color To HSL` | 转换为 HSL | Color | H, S, L | +| `Color Lerp` | 颜色插值 | A, B, T | Color | +| `Color Lighten` | 提亮 | Color, Amount | Color | +| `Color Darken` | 变暗 | Color, Amount | Color | +| `Color Saturate` | 增加饱和度 | Color, Amount | Color | +| `Color Desaturate` | 降低饱和度 | Color, Amount | Color | +| `Color Invert` | 反色 | Color | Color | +| `Color Grayscale` | 灰度化 | Color | Color | +| `Color Luminance` | 获取亮度 | Color | Float | + +### 颜色常量 + +| 节点 | 值 | +|------|------| +| `Color White` | (1, 1, 1, 1) | +| `Color Black` | (0, 0, 0, 1) | +| `Color Red` | (1, 0, 0, 1) | +| `Color Green` | (0, 1, 0, 1) | +| `Color Blue` | (0, 0, 1, 1) | +| `Color Transparent` | (0, 0, 0, 0) | + +### 示例:颜色过渡动画 + +
+ +
+
Color Red
+
+
+ + Color +
+
+
+
+
Color Blue
+
+
+ + Color +
+
+
+
+
Color Lerp
+
+
+ + A +
+
+ + B +
+
+ + T + 0.5 +
+
+ + Result +
+
+
+
+ +### 示例:从 Hex 创建颜色 + +
+ +
+
Color From Hex
+
+
+ + Hex + "#FF5722" +
+
+ + Color +
+
+
+
+
Break Color
+
+
+ + Color +
+
+ + R +
+
+ + G +
+
+ + B +
+
+ + A +
+
+
+
+ +--- + +## 相关文档 + +- [蓝图节点参考](/modules/blueprint/nodes) - 核心蓝图节点 +- [蓝图编辑器指南](/modules/blueprint/editor-guide) - 编辑器使用方法 +- [自定义节点](/modules/blueprint/custom-nodes) - 创建自定义节点 diff --git a/docs/src/content/docs/modules/math/index.md b/docs/src/content/docs/modules/math/index.md new file mode 100644 index 00000000..e7c567a8 --- /dev/null +++ b/docs/src/content/docs/modules/math/index.md @@ -0,0 +1,79 @@ +--- +title: "数学库" +description: "ESEngine 数学库 - Vector2, Fixed32, FixedVector2, Color 等数学类型" +--- + +`@esengine/ecs-framework-math` 模块提供游戏开发常用的数学类型和运算。 + +## 核心类型 + +| 类型 | 说明 | +|------|------| +| `Vector2` | 2D 浮点向量,用于位置、速度、方向 | +| `Fixed32` | Q16.16 定点数,用于帧同步确定性计算 | +| `FixedVector2` | 2D 定点向量,用于确定性物理 | +| `Color` | RGBA 颜色 | + +## 功能特性 + +### Vector2 + +- 加法、减法、缩放 +- 点积、叉积 +- 长度、归一化 +- 距离、插值 +- 旋转、角度转换 + +### Fixed32 定点数 + +专为帧同步网络游戏设计,保证跨平台计算一致性: + +- 基本运算:加、减、乘、除 +- 数学函数:绝对值、平方根、取整 +- 比较、钳制、插值 +- 常量:0、1、0.5、π、2π + +### Color 颜色 + +- RGB/RGBA 创建与分解 +- Hex 十六进制转换 +- HSL 色彩空间转换 +- 颜色操作:提亮、变暗、饱和度调整 +- 颜色混合与插值 + +## 蓝图支持 + +数学库提供了丰富的蓝图节点,详见: + +- [数学库蓝图节点](/modules/math/blueprint-nodes) + +## 安装 + +```bash +pnpm add @esengine/ecs-framework-math +``` + +## 基本用法 + +```typescript +import { Vector2, Fixed32, FixedVector2, Color } from '@esengine/ecs-framework-math'; + +// Vector2 +const pos = new Vector2(10, 20); +const dir = pos.normalized(); + +// Fixed32 (帧同步) +const speed = Fixed32.from(5.0); +const dt = Fixed32.from(0.016); +const distance = speed.mul(dt); + +// FixedVector2 +const fixedPos = FixedVector2.from(10, 20); +const fixedVel = FixedVector2.from(1, 0); +const newPos = fixedPos.add(fixedVel); + +// Color +const red = Color.RED; +const blue = Color.BLUE; +const purple = Color.lerp(red, blue, 0.5); +``` diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css index 33416e10..98d715ca 100644 --- a/docs/src/styles/custom.css +++ b/docs/src/styles/custom.css @@ -296,6 +296,10 @@ nav.sidebar-content ul li a[aria-current="page"] { .bp-conn.component { stroke: #7030c0; } .bp-conn.array { stroke: #7030c0; } .bp-conn.any { stroke: #707070; } +.bp-conn.vector2 { stroke: #2196F3; } +.bp-conn.fixed32 { stroke: #9C27B0; } +.bp-conn.fixedvector2 { stroke: #673AB7; } +.bp-conn.color { stroke: #FF9800; } /* ==================== Node Container ==================== */ .bp-node { diff --git a/packages/framework/blueprint/src/nodes/math/MathOperations.ts b/packages/framework/blueprint/src/nodes/math/MathOperations.ts index 4a2ecc76..52af03eb 100644 --- a/packages/framework/blueprint/src/nodes/math/MathOperations.ts +++ b/packages/framework/blueprint/src/nodes/math/MathOperations.ts @@ -561,3 +561,325 @@ export class SignExecutor implements INodeExecutor { return { outputs: { result: Math.sign(value) } }; } } + +// ============================================================================ +// Wrap Node (循环限制节点) +// ============================================================================ + +export const WrapTemplate: BlueprintNodeTemplate = { + type: 'Wrap', + title: 'Wrap', + category: 'math', + color: '#4CAF50', + description: 'Wraps value to stay within min and max range (将值循环限制在范围内)', + keywords: ['wrap', 'loop', 'cycle', 'range', 'math'], + isPure: true, + inputs: [ + { name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 }, + { name: 'min', type: 'float', displayName: 'Min', defaultValue: 0 }, + { name: 'max', type: 'float', displayName: 'Max', defaultValue: 1 } + ], + outputs: [ + { name: 'result', type: 'float', displayName: 'Result' } + ] +}; + +@RegisterNode(WrapTemplate) +export class WrapExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const value = Number(context.evaluateInput(node.id, 'value', 0)); + const min = Number(context.evaluateInput(node.id, 'min', 0)); + const max = Number(context.evaluateInput(node.id, 'max', 1)); + const range = max - min; + if (range <= 0) return { outputs: { result: min } }; + const wrapped = ((value - min) % range + range) % range + min; + return { outputs: { result: wrapped } }; + } +} + +// ============================================================================ +// Sin Node (正弦节点) +// ============================================================================ + +export const SinTemplate: BlueprintNodeTemplate = { + type: 'Sin', + title: 'Sin', + category: 'math', + color: '#4CAF50', + description: 'Returns the sine of angle in radians (返回弧度角的正弦值)', + keywords: ['sin', 'sine', 'trig', 'math'], + isPure: true, + inputs: [ + { name: 'radians', type: 'float', displayName: 'Radians', defaultValue: 0 } + ], + outputs: [ + { name: 'result', type: 'float', displayName: 'Result' } + ] +}; + +@RegisterNode(SinTemplate) +export class SinExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const radians = Number(context.evaluateInput(node.id, 'radians', 0)); + return { outputs: { result: Math.sin(radians) } }; + } +} + +// ============================================================================ +// Cos Node (余弦节点) +// ============================================================================ + +export const CosTemplate: BlueprintNodeTemplate = { + type: 'Cos', + title: 'Cos', + category: 'math', + color: '#4CAF50', + description: 'Returns the cosine of angle in radians (返回弧度角的余弦值)', + keywords: ['cos', 'cosine', 'trig', 'math'], + isPure: true, + inputs: [ + { name: 'radians', type: 'float', displayName: 'Radians', defaultValue: 0 } + ], + outputs: [ + { name: 'result', type: 'float', displayName: 'Result' } + ] +}; + +@RegisterNode(CosTemplate) +export class CosExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const radians = Number(context.evaluateInput(node.id, 'radians', 0)); + return { outputs: { result: Math.cos(radians) } }; + } +} + +// ============================================================================ +// Tan Node (正切节点) +// ============================================================================ + +export const TanTemplate: BlueprintNodeTemplate = { + type: 'Tan', + title: 'Tan', + category: 'math', + color: '#4CAF50', + description: 'Returns the tangent of angle in radians (返回弧度角的正切值)', + keywords: ['tan', 'tangent', 'trig', 'math'], + isPure: true, + inputs: [ + { name: 'radians', type: 'float', displayName: 'Radians', defaultValue: 0 } + ], + outputs: [ + { name: 'result', type: 'float', displayName: 'Result' } + ] +}; + +@RegisterNode(TanTemplate) +export class TanExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const radians = Number(context.evaluateInput(node.id, 'radians', 0)); + return { outputs: { result: Math.tan(radians) } }; + } +} + +// ============================================================================ +// Asin Node (反正弦节点) +// ============================================================================ + +export const AsinTemplate: BlueprintNodeTemplate = { + type: 'Asin', + title: 'Asin', + category: 'math', + color: '#4CAF50', + description: 'Returns the arc sine in radians (返回反正弦值,单位为弧度)', + keywords: ['asin', 'arc', 'sine', 'inverse', 'trig', 'math'], + isPure: true, + inputs: [ + { name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 } + ], + outputs: [ + { name: 'result', type: 'float', displayName: 'Radians' } + ] +}; + +@RegisterNode(AsinTemplate) +export class AsinExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const value = Number(context.evaluateInput(node.id, 'value', 0)); + return { outputs: { result: Math.asin(Math.max(-1, Math.min(1, value))) } }; + } +} + +// ============================================================================ +// Acos Node (反余弦节点) +// ============================================================================ + +export const AcosTemplate: BlueprintNodeTemplate = { + type: 'Acos', + title: 'Acos', + category: 'math', + color: '#4CAF50', + description: 'Returns the arc cosine in radians (返回反余弦值,单位为弧度)', + keywords: ['acos', 'arc', 'cosine', 'inverse', 'trig', 'math'], + isPure: true, + inputs: [ + { name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 } + ], + outputs: [ + { name: 'result', type: 'float', displayName: 'Radians' } + ] +}; + +@RegisterNode(AcosTemplate) +export class AcosExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const value = Number(context.evaluateInput(node.id, 'value', 0)); + return { outputs: { result: Math.acos(Math.max(-1, Math.min(1, value))) } }; + } +} + +// ============================================================================ +// Atan Node (反正切节点) +// ============================================================================ + +export const AtanTemplate: BlueprintNodeTemplate = { + type: 'Atan', + title: 'Atan', + category: 'math', + color: '#4CAF50', + description: 'Returns the arc tangent in radians (返回反正切值,单位为弧度)', + keywords: ['atan', 'arc', 'tangent', 'inverse', 'trig', 'math'], + isPure: true, + inputs: [ + { name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 } + ], + outputs: [ + { name: 'result', type: 'float', displayName: 'Radians' } + ] +}; + +@RegisterNode(AtanTemplate) +export class AtanExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const value = Number(context.evaluateInput(node.id, 'value', 0)); + return { outputs: { result: Math.atan(value) } }; + } +} + +// ============================================================================ +// Atan2 Node (两参数反正切节点) +// ============================================================================ + +export const Atan2Template: BlueprintNodeTemplate = { + type: 'Atan2', + title: 'Atan2', + category: 'math', + color: '#4CAF50', + description: 'Returns the angle in radians between the positive X axis and the point (x, y) (返回点(x,y)与正X轴之间的弧度角)', + keywords: ['atan2', 'angle', 'direction', 'trig', 'math'], + isPure: true, + inputs: [ + { name: 'y', type: 'float', displayName: 'Y', defaultValue: 0 }, + { name: 'x', type: 'float', displayName: 'X', defaultValue: 1 } + ], + outputs: [ + { name: 'result', type: 'float', displayName: 'Radians' } + ] +}; + +@RegisterNode(Atan2Template) +export class Atan2Executor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const y = Number(context.evaluateInput(node.id, 'y', 0)); + const x = Number(context.evaluateInput(node.id, 'x', 1)); + return { outputs: { result: Math.atan2(y, x) } }; + } +} + +// ============================================================================ +// Degrees to Radians Node (角度转弧度节点) +// ============================================================================ + +export const DegToRadTemplate: BlueprintNodeTemplate = { + type: 'DegToRad', + title: 'Degrees to Radians', + category: 'math', + color: '#4CAF50', + description: 'Converts degrees to radians (将角度转换为弧度)', + keywords: ['degrees', 'radians', 'convert', 'angle', 'math'], + isPure: true, + inputs: [ + { name: 'degrees', type: 'float', displayName: 'Degrees', defaultValue: 0 } + ], + outputs: [ + { name: 'result', type: 'float', displayName: 'Radians' } + ] +}; + +@RegisterNode(DegToRadTemplate) +export class DegToRadExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const degrees = Number(context.evaluateInput(node.id, 'degrees', 0)); + return { outputs: { result: degrees * (Math.PI / 180) } }; + } +} + +// ============================================================================ +// Radians to Degrees Node (弧度转角度节点) +// ============================================================================ + +export const RadToDegTemplate: BlueprintNodeTemplate = { + type: 'RadToDeg', + title: 'Radians to Degrees', + category: 'math', + color: '#4CAF50', + description: 'Converts radians to degrees (将弧度转换为角度)', + keywords: ['radians', 'degrees', 'convert', 'angle', 'math'], + isPure: true, + inputs: [ + { name: 'radians', type: 'float', displayName: 'Radians', defaultValue: 0 } + ], + outputs: [ + { name: 'result', type: 'float', displayName: 'Degrees' } + ] +}; + +@RegisterNode(RadToDegTemplate) +export class RadToDegExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const radians = Number(context.evaluateInput(node.id, 'radians', 0)); + return { outputs: { result: radians * (180 / Math.PI) } }; + } +} + +// ============================================================================ +// Inverse Lerp Node (反向线性插值节点) +// ============================================================================ + +export const InverseLerpTemplate: BlueprintNodeTemplate = { + type: 'InverseLerp', + title: 'Inverse Lerp', + category: 'math', + color: '#4CAF50', + description: 'Returns the percentage of Value between A and B (返回值在 A 和 B 之间的百分比位置)', + keywords: ['inverse', 'lerp', 'percentage', 'ratio', 'math'], + isPure: true, + inputs: [ + { name: 'a', type: 'float', displayName: 'A', defaultValue: 0 }, + { name: 'b', type: 'float', displayName: 'B', defaultValue: 1 }, + { name: 'value', type: 'float', displayName: 'Value', defaultValue: 0.5 } + ], + outputs: [ + { name: 'result', type: 'float', displayName: 'Alpha (0-1)' } + ] +}; + +@RegisterNode(InverseLerpTemplate) +export class InverseLerpExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult { + const a = Number(context.evaluateInput(node.id, 'a', 0)); + const b = Number(context.evaluateInput(node.id, 'b', 1)); + const value = Number(context.evaluateInput(node.id, 'value', 0.5)); + if (b === a) return { outputs: { result: 0 } }; + return { outputs: { result: (value - a) / (b - a) } }; + } +} diff --git a/packages/framework/math/package.json b/packages/framework/math/package.json index f9e2d826..8ad3089f 100644 --- a/packages/framework/math/package.json +++ b/packages/framework/math/package.json @@ -40,6 +40,9 @@ }, "author": "yhh", "license": "MIT", + "dependencies": { + "@esengine/blueprint": "workspace:*" + }, "devDependencies": { "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-node-resolve": "^16.0.1", diff --git a/packages/framework/math/src/index.ts b/packages/framework/math/src/index.ts index b60d7903..c549fc9d 100644 --- a/packages/framework/math/src/index.ts +++ b/packages/framework/math/src/index.ts @@ -33,3 +33,6 @@ export * from './Collision'; // 动画和插值 export * from './Animation'; + +// 蓝图节点 +export * from './nodes'; diff --git a/packages/framework/math/src/nodes/ColorNodes.ts b/packages/framework/math/src/nodes/ColorNodes.ts new file mode 100644 index 00000000..be4cd936 --- /dev/null +++ b/packages/framework/math/src/nodes/ColorNodes.ts @@ -0,0 +1,463 @@ +/** + * @zh 颜色蓝图节点 + * @en Color Blueprint Nodes + */ + +import type { BlueprintNodeTemplate, BlueprintNode, INodeExecutor, ExecutionResult } from '@esengine/blueprint'; +import { Color } from '../Color'; + +interface ColorContext { + evaluateInput(nodeId: string, pinName: string, defaultValue?: unknown): unknown; +} + +// Make Color from RGBA +export const MakeColorTemplate: BlueprintNodeTemplate = { + type: 'MakeColor', + title: 'Make Color', + category: 'math', + description: 'Creates a Color from RGBA', + keywords: ['make', 'create', 'color', 'rgba'], + menuPath: ['Math', 'Color', 'Make Color'], + isPure: true, + inputs: [ + { name: 'r', displayName: 'R (0-255)', type: 'int', defaultValue: 255 }, + { name: 'g', displayName: 'G (0-255)', type: 'int', defaultValue: 255 }, + { name: 'b', displayName: 'B (0-255)', type: 'int', defaultValue: 255 }, + { name: 'a', displayName: 'A (0-1)', type: 'float', defaultValue: 1 } + ], + outputs: [ + { name: 'color', displayName: 'Color', type: 'color' } + ], + color: '#E91E63' +}; + +export class MakeColorExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const r = Number(ctx.evaluateInput(node.id, 'r', 255)); + const g = Number(ctx.evaluateInput(node.id, 'g', 255)); + const b = Number(ctx.evaluateInput(node.id, 'b', 255)); + const a = Number(ctx.evaluateInput(node.id, 'a', 1)); + return { outputs: { color: new Color(r, g, b, a) } }; + } +} + +// Break Color +export const BreakColorTemplate: BlueprintNodeTemplate = { + type: 'BreakColor', + title: 'Break Color', + category: 'math', + description: 'Breaks a Color into RGBA', + keywords: ['break', 'split', 'color', 'rgba'], + menuPath: ['Math', 'Color', 'Break Color'], + isPure: true, + inputs: [ + { name: 'color', displayName: 'Color', type: 'color' } + ], + outputs: [ + { name: 'r', displayName: 'R', type: 'int' }, + { name: 'g', displayName: 'G', type: 'int' }, + { name: 'b', displayName: 'B', type: 'int' }, + { name: 'a', displayName: 'A', type: 'float' } + ], + color: '#E91E63' +}; + +export class BreakColorExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const color = ctx.evaluateInput(node.id, 'color', Color.WHITE) as Color; + const c = color ?? Color.WHITE; + return { outputs: { r: c.r, g: c.g, b: c.b, a: c.a } }; + } +} + +// Color from Hex +export const ColorFromHexTemplate: BlueprintNodeTemplate = { + type: 'ColorFromHex', + title: 'Color From Hex', + category: 'math', + description: 'Creates a Color from hex string', + keywords: ['color', 'hex', 'from', 'create'], + menuPath: ['Math', 'Color', 'From Hex'], + isPure: true, + inputs: [ + { name: 'hex', displayName: 'Hex', type: 'string', defaultValue: '#FFFFFF' }, + { name: 'alpha', displayName: 'Alpha', type: 'float', defaultValue: 1 } + ], + outputs: [ + { name: 'color', displayName: 'Color', type: 'color' } + ], + color: '#E91E63' +}; + +export class ColorFromHexExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const hex = String(ctx.evaluateInput(node.id, 'hex', '#FFFFFF')); + const alpha = Number(ctx.evaluateInput(node.id, 'alpha', 1)); + return { outputs: { color: Color.fromHex(hex, alpha) } }; + } +} + +// Color to Hex +export const ColorToHexTemplate: BlueprintNodeTemplate = { + type: 'ColorToHex', + title: 'Color To Hex', + category: 'math', + description: 'Converts a Color to hex string', + keywords: ['color', 'hex', 'to', 'convert'], + menuPath: ['Math', 'Color', 'To Hex'], + isPure: true, + inputs: [ + { name: 'color', displayName: 'Color', type: 'color' } + ], + outputs: [ + { name: 'hex', displayName: 'Hex', type: 'string' } + ], + color: '#E91E63' +}; + +export class ColorToHexExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const color = ctx.evaluateInput(node.id, 'color', Color.WHITE) as Color; + return { outputs: { hex: (color ?? Color.WHITE).toHex() } }; + } +} + +// Color from HSL +export const ColorFromHSLTemplate: BlueprintNodeTemplate = { + type: 'ColorFromHSL', + title: 'Color From HSL', + category: 'math', + description: 'Creates a Color from HSL values', + keywords: ['color', 'hsl', 'hue', 'saturation', 'lightness'], + menuPath: ['Math', 'Color', 'From HSL'], + isPure: true, + inputs: [ + { name: 'h', displayName: 'H (0-360)', type: 'float', defaultValue: 0 }, + { name: 's', displayName: 'S (0-1)', type: 'float', defaultValue: 1 }, + { name: 'l', displayName: 'L (0-1)', type: 'float', defaultValue: 0.5 }, + { name: 'a', displayName: 'A (0-1)', type: 'float', defaultValue: 1 } + ], + outputs: [ + { name: 'color', displayName: 'Color', type: 'color' } + ], + color: '#E91E63' +}; + +export class ColorFromHSLExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const h = Number(ctx.evaluateInput(node.id, 'h', 0)); + const s = Number(ctx.evaluateInput(node.id, 's', 1)); + const l = Number(ctx.evaluateInput(node.id, 'l', 0.5)); + const a = Number(ctx.evaluateInput(node.id, 'a', 1)); + return { outputs: { color: Color.fromHSL(h, s, l, a) } }; + } +} + +// Color to HSL +export const ColorToHSLTemplate: BlueprintNodeTemplate = { + type: 'ColorToHSL', + title: 'Color To HSL', + category: 'math', + description: 'Converts a Color to HSL values', + keywords: ['color', 'hsl', 'convert'], + menuPath: ['Math', 'Color', 'To HSL'], + isPure: true, + inputs: [ + { name: 'color', displayName: 'Color', type: 'color' } + ], + outputs: [ + { name: 'h', displayName: 'H', type: 'float' }, + { name: 's', displayName: 'S', type: 'float' }, + { name: 'l', displayName: 'L', type: 'float' } + ], + color: '#E91E63' +}; + +export class ColorToHSLExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const color = ctx.evaluateInput(node.id, 'color', Color.WHITE) as Color; + const hsl = (color ?? Color.WHITE).toHSL(); + return { outputs: { h: hsl.h, s: hsl.s, l: hsl.l } }; + } +} + +// Color Lerp +export const ColorLerpTemplate: BlueprintNodeTemplate = { + type: 'ColorLerp', + title: 'Color Lerp', + category: 'math', + description: 'Linear interpolation between two colors', + keywords: ['color', 'lerp', 'interpolate', 'blend'], + menuPath: ['Math', 'Color', 'Lerp'], + isPure: true, + inputs: [ + { name: 'from', displayName: 'From', type: 'color' }, + { name: 'to', displayName: 'To', type: 'color' }, + { name: 't', displayName: 'T', type: 'float', defaultValue: 0.5 } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'color' } + ], + color: '#E91E63' +}; + +export class ColorLerpExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const from = ctx.evaluateInput(node.id, 'from', Color.BLACK) as Color; + const to = ctx.evaluateInput(node.id, 'to', Color.WHITE) as Color; + const t = Number(ctx.evaluateInput(node.id, 't', 0.5)); + return { outputs: { result: Color.lerp(from ?? Color.BLACK, to ?? Color.WHITE, t) } }; + } +} + +// Color Lighten +export const ColorLightenTemplate: BlueprintNodeTemplate = { + type: 'ColorLighten', + title: 'Color Lighten', + category: 'math', + description: 'Lightens a color', + keywords: ['color', 'lighten', 'bright'], + menuPath: ['Math', 'Color', 'Lighten'], + isPure: true, + inputs: [ + { name: 'color', displayName: 'Color', type: 'color' }, + { name: 'amount', displayName: 'Amount', type: 'float', defaultValue: 0.1 } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'color' } + ], + color: '#E91E63' +}; + +export class ColorLightenExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const color = ctx.evaluateInput(node.id, 'color', Color.GRAY) as Color; + const amount = Number(ctx.evaluateInput(node.id, 'amount', 0.1)); + return { outputs: { result: Color.lighten(color ?? Color.GRAY, amount) } }; + } +} + +// Color Darken +export const ColorDarkenTemplate: BlueprintNodeTemplate = { + type: 'ColorDarken', + title: 'Color Darken', + category: 'math', + description: 'Darkens a color', + keywords: ['color', 'darken', 'dark'], + menuPath: ['Math', 'Color', 'Darken'], + isPure: true, + inputs: [ + { name: 'color', displayName: 'Color', type: 'color' }, + { name: 'amount', displayName: 'Amount', type: 'float', defaultValue: 0.1 } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'color' } + ], + color: '#E91E63' +}; + +export class ColorDarkenExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const color = ctx.evaluateInput(node.id, 'color', Color.GRAY) as Color; + const amount = Number(ctx.evaluateInput(node.id, 'amount', 0.1)); + return { outputs: { result: Color.darken(color ?? Color.GRAY, amount) } }; + } +} + +// Color Saturate +export const ColorSaturateTemplate: BlueprintNodeTemplate = { + type: 'ColorSaturate', + title: 'Color Saturate', + category: 'math', + description: 'Increases color saturation', + keywords: ['color', 'saturate', 'saturation'], + menuPath: ['Math', 'Color', 'Saturate'], + isPure: true, + inputs: [ + { name: 'color', displayName: 'Color', type: 'color' }, + { name: 'amount', displayName: 'Amount', type: 'float', defaultValue: 0.1 } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'color' } + ], + color: '#E91E63' +}; + +export class ColorSaturateExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const color = ctx.evaluateInput(node.id, 'color', Color.GRAY) as Color; + const amount = Number(ctx.evaluateInput(node.id, 'amount', 0.1)); + return { outputs: { result: Color.saturate(color ?? Color.GRAY, amount) } }; + } +} + +// Color Desaturate +export const ColorDesaturateTemplate: BlueprintNodeTemplate = { + type: 'ColorDesaturate', + title: 'Color Desaturate', + category: 'math', + description: 'Decreases color saturation', + keywords: ['color', 'desaturate', 'saturation'], + menuPath: ['Math', 'Color', 'Desaturate'], + isPure: true, + inputs: [ + { name: 'color', displayName: 'Color', type: 'color' }, + { name: 'amount', displayName: 'Amount', type: 'float', defaultValue: 0.1 } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'color' } + ], + color: '#E91E63' +}; + +export class ColorDesaturateExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const color = ctx.evaluateInput(node.id, 'color', Color.GRAY) as Color; + const amount = Number(ctx.evaluateInput(node.id, 'amount', 0.1)); + return { outputs: { result: Color.desaturate(color ?? Color.GRAY, amount) } }; + } +} + +// Color Invert +export const ColorInvertTemplate: BlueprintNodeTemplate = { + type: 'ColorInvert', + title: 'Color Invert', + category: 'math', + description: 'Inverts a color', + keywords: ['color', 'invert', 'inverse'], + menuPath: ['Math', 'Color', 'Invert'], + isPure: true, + inputs: [ + { name: 'color', displayName: 'Color', type: 'color' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'color' } + ], + color: '#E91E63' +}; + +export class ColorInvertExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const color = ctx.evaluateInput(node.id, 'color', Color.WHITE) as Color; + return { outputs: { result: Color.invert(color ?? Color.WHITE) } }; + } +} + +// Color Grayscale +export const ColorGrayscaleTemplate: BlueprintNodeTemplate = { + type: 'ColorGrayscale', + title: 'Color Grayscale', + category: 'math', + description: 'Converts color to grayscale', + keywords: ['color', 'grayscale', 'gray', 'grey'], + menuPath: ['Math', 'Color', 'Grayscale'], + isPure: true, + inputs: [ + { name: 'color', displayName: 'Color', type: 'color' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'color' } + ], + color: '#E91E63' +}; + +export class ColorGrayscaleExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const color = ctx.evaluateInput(node.id, 'color', Color.WHITE) as Color; + return { outputs: { result: Color.grayscale(color ?? Color.WHITE) } }; + } +} + +// Color Luminance +export const ColorLuminanceTemplate: BlueprintNodeTemplate = { + type: 'ColorLuminance', + title: 'Color Luminance', + category: 'math', + description: 'Gets perceived brightness (0-1)', + keywords: ['color', 'luminance', 'brightness'], + menuPath: ['Math', 'Color', 'Luminance'], + isPure: true, + inputs: [ + { name: 'color', displayName: 'Color', type: 'color' } + ], + outputs: [ + { name: 'luminance', displayName: 'Luminance', type: 'float' } + ], + color: '#E91E63' +}; + +export class ColorLuminanceExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as ColorContext; + const color = ctx.evaluateInput(node.id, 'color', Color.WHITE) as Color; + return { outputs: { luminance: Color.luminance(color ?? Color.WHITE) } }; + } +} + +// Color Constants +export const ColorConstantsTemplate: BlueprintNodeTemplate = { + type: 'ColorConstants', + title: 'Color Constants', + category: 'math', + description: 'Common color constants', + keywords: ['color', 'constant', 'red', 'green', 'blue', 'white', 'black'], + menuPath: ['Math', 'Color', 'Constants'], + isPure: true, + inputs: [], + outputs: [ + { name: 'white', displayName: 'White', type: 'color' }, + { name: 'black', displayName: 'Black', type: 'color' }, + { name: 'red', displayName: 'Red', type: 'color' }, + { name: 'green', displayName: 'Green', type: 'color' }, + { name: 'blue', displayName: 'Blue', type: 'color' }, + { name: 'transparent', displayName: 'Transparent', type: 'color' } + ], + color: '#E91E63' +}; + +export class ColorConstantsExecutor implements INodeExecutor { + execute(): ExecutionResult { + return { + outputs: { + white: Color.WHITE, + black: Color.BLACK, + red: Color.RED, + green: Color.GREEN, + blue: Color.BLUE, + transparent: Color.TRANSPARENT + } + }; + } +} + +// Node definitions collection +export const ColorNodeDefinitions = [ + { template: MakeColorTemplate, executor: new MakeColorExecutor() }, + { template: BreakColorTemplate, executor: new BreakColorExecutor() }, + { template: ColorFromHexTemplate, executor: new ColorFromHexExecutor() }, + { template: ColorToHexTemplate, executor: new ColorToHexExecutor() }, + { template: ColorFromHSLTemplate, executor: new ColorFromHSLExecutor() }, + { template: ColorToHSLTemplate, executor: new ColorToHSLExecutor() }, + { template: ColorLerpTemplate, executor: new ColorLerpExecutor() }, + { template: ColorLightenTemplate, executor: new ColorLightenExecutor() }, + { template: ColorDarkenTemplate, executor: new ColorDarkenExecutor() }, + { template: ColorSaturateTemplate, executor: new ColorSaturateExecutor() }, + { template: ColorDesaturateTemplate, executor: new ColorDesaturateExecutor() }, + { template: ColorInvertTemplate, executor: new ColorInvertExecutor() }, + { template: ColorGrayscaleTemplate, executor: new ColorGrayscaleExecutor() }, + { template: ColorLuminanceTemplate, executor: new ColorLuminanceExecutor() }, + { template: ColorConstantsTemplate, executor: new ColorConstantsExecutor() } +]; diff --git a/packages/framework/math/src/nodes/FixedNodes.ts b/packages/framework/math/src/nodes/FixedNodes.ts new file mode 100644 index 00000000..7a99f01f --- /dev/null +++ b/packages/framework/math/src/nodes/FixedNodes.ts @@ -0,0 +1,662 @@ +/** + * @zh Fixed32 定点数蓝图节点 + * @en Fixed32 Blueprint Nodes + */ + +import type { BlueprintNodeTemplate, BlueprintNode, INodeExecutor, ExecutionResult } from '@esengine/blueprint'; +import { Fixed32 } from '../Fixed32'; + +interface FixedContext { + evaluateInput(nodeId: string, pinName: string, defaultValue?: unknown): unknown; +} + +// Make Fixed32 from float +export const Fixed32FromTemplate: BlueprintNodeTemplate = { + type: 'Fixed32From', + title: 'Fixed32 From Float', + category: 'math', + description: 'Creates Fixed32 from float', + keywords: ['fixed', 'fixed32', 'from', 'create', 'deterministic'], + menuPath: ['Math', 'Fixed', 'From Float'], + isPure: true, + inputs: [ + { name: 'value', displayName: 'Value', type: 'float', defaultValue: 0 } + ], + outputs: [ + { name: 'fixed', displayName: 'Fixed32', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32FromExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const value = Number(ctx.evaluateInput(node.id, 'value', 0)); + return { outputs: { fixed: Fixed32.from(value) } }; + } +} + +// Make Fixed32 from int +export const Fixed32FromIntTemplate: BlueprintNodeTemplate = { + type: 'Fixed32FromInt', + title: 'Fixed32 From Int', + category: 'math', + description: 'Creates Fixed32 from integer (no precision loss)', + keywords: ['fixed', 'fixed32', 'from', 'int', 'integer'], + menuPath: ['Math', 'Fixed', 'From Int'], + isPure: true, + inputs: [ + { name: 'value', displayName: 'Value', type: 'int', defaultValue: 0 } + ], + outputs: [ + { name: 'fixed', displayName: 'Fixed32', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32FromIntExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const value = Math.floor(Number(ctx.evaluateInput(node.id, 'value', 0))); + return { outputs: { fixed: Fixed32.fromInt(value) } }; + } +} + +// Fixed32 to float +export const Fixed32ToFloatTemplate: BlueprintNodeTemplate = { + type: 'Fixed32ToFloat', + title: 'Fixed32 To Float', + category: 'math', + description: 'Converts Fixed32 to float', + keywords: ['fixed', 'fixed32', 'to', 'float', 'convert'], + menuPath: ['Math', 'Fixed', 'To Float'], + isPure: true, + inputs: [ + { name: 'fixed', displayName: 'Fixed32', type: 'object' } + ], + outputs: [ + { name: 'value', displayName: 'Value', type: 'float' } + ], + color: '#9C27B0' +}; + +export class Fixed32ToFloatExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const fixed = ctx.evaluateInput(node.id, 'fixed', Fixed32.ZERO) as Fixed32; + return { outputs: { value: fixed?.toNumber() ?? 0 } }; + } +} + +// Fixed32 to int +export const Fixed32ToIntTemplate: BlueprintNodeTemplate = { + type: 'Fixed32ToInt', + title: 'Fixed32 To Int', + category: 'math', + description: 'Converts Fixed32 to integer (floor)', + keywords: ['fixed', 'fixed32', 'to', 'int', 'integer'], + menuPath: ['Math', 'Fixed', 'To Int'], + isPure: true, + inputs: [ + { name: 'fixed', displayName: 'Fixed32', type: 'object' } + ], + outputs: [ + { name: 'value', displayName: 'Value', type: 'int' } + ], + color: '#9C27B0' +}; + +export class Fixed32ToIntExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const fixed = ctx.evaluateInput(node.id, 'fixed', Fixed32.ZERO) as Fixed32; + return { outputs: { value: fixed?.toInt() ?? 0 } }; + } +} + +// Fixed32 Add +export const Fixed32AddTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Add', + title: 'Fixed32 +', + category: 'math', + description: 'Adds two Fixed32 values', + keywords: ['fixed', 'add', 'plus', '+'], + menuPath: ['Math', 'Fixed', 'Add'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32AddExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32; + const b = ctx.evaluateInput(node.id, 'b', Fixed32.ZERO) as Fixed32; + return { outputs: { result: (a ?? Fixed32.ZERO).add(b ?? Fixed32.ZERO) } }; + } +} + +// Fixed32 Subtract +export const Fixed32SubtractTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Subtract', + title: 'Fixed32 -', + category: 'math', + description: 'Subtracts B from A', + keywords: ['fixed', 'subtract', 'minus', '-'], + menuPath: ['Math', 'Fixed', 'Subtract'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32SubtractExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32; + const b = ctx.evaluateInput(node.id, 'b', Fixed32.ZERO) as Fixed32; + return { outputs: { result: (a ?? Fixed32.ZERO).sub(b ?? Fixed32.ZERO) } }; + } +} + +// Fixed32 Multiply +export const Fixed32MultiplyTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Multiply', + title: 'Fixed32 *', + category: 'math', + description: 'Multiplies two Fixed32 values', + keywords: ['fixed', 'multiply', 'times', '*'], + menuPath: ['Math', 'Fixed', 'Multiply'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32MultiplyExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const a = ctx.evaluateInput(node.id, 'a', Fixed32.ONE) as Fixed32; + const b = ctx.evaluateInput(node.id, 'b', Fixed32.ONE) as Fixed32; + return { outputs: { result: (a ?? Fixed32.ONE).mul(b ?? Fixed32.ONE) } }; + } +} + +// Fixed32 Divide +export const Fixed32DivideTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Divide', + title: 'Fixed32 /', + category: 'math', + description: 'Divides A by B', + keywords: ['fixed', 'divide', '/'], + menuPath: ['Math', 'Fixed', 'Divide'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32DivideExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32; + const b = ctx.evaluateInput(node.id, 'b', Fixed32.ONE) as Fixed32; + const divisor = b ?? Fixed32.ONE; + if (divisor.isZero()) { + return { outputs: { result: Fixed32.ZERO } }; + } + return { outputs: { result: (a ?? Fixed32.ZERO).div(divisor) } }; + } +} + +// Fixed32 Negate +export const Fixed32NegateTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Negate', + title: 'Fixed32 Negate', + category: 'math', + description: 'Negates a Fixed32 value', + keywords: ['fixed', 'negate', '-'], + menuPath: ['Math', 'Fixed', 'Negate'], + isPure: true, + inputs: [ + { name: 'value', displayName: 'Value', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32NegateExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32; + return { outputs: { result: (value ?? Fixed32.ZERO).neg() } }; + } +} + +// Fixed32 Abs +export const Fixed32AbsTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Abs', + title: 'Fixed32 Abs', + category: 'math', + description: 'Absolute value of Fixed32', + keywords: ['fixed', 'abs', 'absolute'], + menuPath: ['Math', 'Fixed', 'Abs'], + isPure: true, + inputs: [ + { name: 'value', displayName: 'Value', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32AbsExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32; + return { outputs: { result: (value ?? Fixed32.ZERO).abs() } }; + } +} + +// Fixed32 Sqrt +export const Fixed32SqrtTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Sqrt', + title: 'Fixed32 Sqrt', + category: 'math', + description: 'Square root (deterministic)', + keywords: ['fixed', 'sqrt', 'square', 'root'], + menuPath: ['Math', 'Fixed', 'Sqrt'], + isPure: true, + inputs: [ + { name: 'value', displayName: 'Value', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32SqrtExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32; + return { outputs: { result: Fixed32.sqrt(value ?? Fixed32.ZERO) } }; + } +} + +// Fixed32 Floor +export const Fixed32FloorTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Floor', + title: 'Fixed32 Floor', + category: 'math', + description: 'Floor of Fixed32', + keywords: ['fixed', 'floor', 'round', 'down'], + menuPath: ['Math', 'Fixed', 'Floor'], + isPure: true, + inputs: [ + { name: 'value', displayName: 'Value', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32FloorExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32; + return { outputs: { result: Fixed32.floor(value ?? Fixed32.ZERO) } }; + } +} + +// Fixed32 Ceil +export const Fixed32CeilTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Ceil', + title: 'Fixed32 Ceil', + category: 'math', + description: 'Ceiling of Fixed32', + keywords: ['fixed', 'ceil', 'ceiling', 'round', 'up'], + menuPath: ['Math', 'Fixed', 'Ceil'], + isPure: true, + inputs: [ + { name: 'value', displayName: 'Value', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32CeilExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32; + return { outputs: { result: Fixed32.ceil(value ?? Fixed32.ZERO) } }; + } +} + +// Fixed32 Round +export const Fixed32RoundTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Round', + title: 'Fixed32 Round', + category: 'math', + description: 'Rounds Fixed32 to nearest integer', + keywords: ['fixed', 'round'], + menuPath: ['Math', 'Fixed', 'Round'], + isPure: true, + inputs: [ + { name: 'value', displayName: 'Value', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32RoundExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32; + return { outputs: { result: Fixed32.round(value ?? Fixed32.ZERO) } }; + } +} + +// Fixed32 Sign +export const Fixed32SignTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Sign', + title: 'Fixed32 Sign', + category: 'math', + description: 'Sign of Fixed32 (-1, 0, or 1)', + keywords: ['fixed', 'sign'], + menuPath: ['Math', 'Fixed', 'Sign'], + isPure: true, + inputs: [ + { name: 'value', displayName: 'Value', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32SignExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32; + return { outputs: { result: Fixed32.sign(value ?? Fixed32.ZERO) } }; + } +} + +// Fixed32 Min +export const Fixed32MinTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Min', + title: 'Fixed32 Min', + category: 'math', + description: 'Minimum of two Fixed32 values', + keywords: ['fixed', 'min', 'minimum'], + menuPath: ['Math', 'Fixed', 'Min'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32MinExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32; + const b = ctx.evaluateInput(node.id, 'b', Fixed32.ZERO) as Fixed32; + return { outputs: { result: Fixed32.min(a ?? Fixed32.ZERO, b ?? Fixed32.ZERO) } }; + } +} + +// Fixed32 Max +export const Fixed32MaxTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Max', + title: 'Fixed32 Max', + category: 'math', + description: 'Maximum of two Fixed32 values', + keywords: ['fixed', 'max', 'maximum'], + menuPath: ['Math', 'Fixed', 'Max'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32MaxExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32; + const b = ctx.evaluateInput(node.id, 'b', Fixed32.ZERO) as Fixed32; + return { outputs: { result: Fixed32.max(a ?? Fixed32.ZERO, b ?? Fixed32.ZERO) } }; + } +} + +// Fixed32 Clamp +export const Fixed32ClampTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Clamp', + title: 'Fixed32 Clamp', + category: 'math', + description: 'Clamps Fixed32 to range', + keywords: ['fixed', 'clamp', 'limit', 'range'], + menuPath: ['Math', 'Fixed', 'Clamp'], + isPure: true, + inputs: [ + { name: 'value', displayName: 'Value', type: 'object' }, + { name: 'min', displayName: 'Min', type: 'object' }, + { name: 'max', displayName: 'Max', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32ClampExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32; + const min = ctx.evaluateInput(node.id, 'min', Fixed32.ZERO) as Fixed32; + const max = ctx.evaluateInput(node.id, 'max', Fixed32.ONE) as Fixed32; + return { outputs: { result: Fixed32.clamp(value ?? Fixed32.ZERO, min ?? Fixed32.ZERO, max ?? Fixed32.ONE) } }; + } +} + +// Fixed32 Lerp +export const Fixed32LerpTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Lerp', + title: 'Fixed32 Lerp', + category: 'math', + description: 'Linear interpolation between A and B', + keywords: ['fixed', 'lerp', 'interpolate', 'blend'], + menuPath: ['Math', 'Fixed', 'Lerp'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' }, + { name: 't', displayName: 'T', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32LerpExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32; + const b = ctx.evaluateInput(node.id, 'b', Fixed32.ONE) as Fixed32; + const t = ctx.evaluateInput(node.id, 't', Fixed32.HALF) as Fixed32; + return { outputs: { result: Fixed32.lerp(a ?? Fixed32.ZERO, b ?? Fixed32.ONE, t ?? Fixed32.HALF) } }; + } +} + +// Fixed32 Compare +export const Fixed32CompareTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Compare', + title: 'Fixed32 Compare', + category: 'math', + description: 'Compares two Fixed32 values', + keywords: ['fixed', 'compare', 'equal', 'less', 'greater'], + menuPath: ['Math', 'Fixed', 'Compare'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' } + ], + outputs: [ + { name: 'equal', displayName: 'A == B', type: 'bool' }, + { name: 'less', displayName: 'A < B', type: 'bool' }, + { name: 'greater', displayName: 'A > B', type: 'bool' } + ], + color: '#9C27B0' +}; + +export class Fixed32CompareExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32; + const b = ctx.evaluateInput(node.id, 'b', Fixed32.ZERO) as Fixed32; + const aVal = a ?? Fixed32.ZERO; + const bVal = b ?? Fixed32.ZERO; + return { + outputs: { + equal: aVal.eq(bVal), + less: aVal.lt(bVal), + greater: aVal.gt(bVal) + } + }; + } +} + +// Fixed32 IsZero +export const Fixed32IsZeroTemplate: BlueprintNodeTemplate = { + type: 'Fixed32IsZero', + title: 'Fixed32 Is Zero', + category: 'math', + description: 'Checks if Fixed32 is zero, positive, or negative', + keywords: ['fixed', 'zero', 'check'], + menuPath: ['Math', 'Fixed', 'Is Zero'], + isPure: true, + inputs: [ + { name: 'value', displayName: 'Value', type: 'object' } + ], + outputs: [ + { name: 'isZero', displayName: 'Is Zero', type: 'bool' }, + { name: 'isPositive', displayName: 'Is Positive', type: 'bool' }, + { name: 'isNegative', displayName: 'Is Negative', type: 'bool' } + ], + color: '#9C27B0' +}; + +export class Fixed32IsZeroExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedContext; + const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32; + const val = value ?? Fixed32.ZERO; + return { + outputs: { + isZero: val.isZero(), + isPositive: val.isPositive(), + isNegative: val.isNegative() + } + }; + } +} + +// Fixed32 Constants +export const Fixed32ConstantsTemplate: BlueprintNodeTemplate = { + type: 'Fixed32Constants', + title: 'Fixed32 Constants', + category: 'math', + description: 'Common Fixed32 constants', + keywords: ['fixed', 'constant', 'pi', 'zero', 'one'], + menuPath: ['Math', 'Fixed', 'Constants'], + isPure: true, + inputs: [], + outputs: [ + { name: 'zero', displayName: '0', type: 'object' }, + { name: 'one', displayName: '1', type: 'object' }, + { name: 'half', displayName: '0.5', type: 'object' }, + { name: 'pi', displayName: 'PI', type: 'object' }, + { name: 'twoPi', displayName: '2PI', type: 'object' } + ], + color: '#9C27B0' +}; + +export class Fixed32ConstantsExecutor implements INodeExecutor { + execute(): ExecutionResult { + return { + outputs: { + zero: Fixed32.ZERO, + one: Fixed32.ONE, + half: Fixed32.HALF, + pi: Fixed32.PI, + twoPi: Fixed32.TWO_PI + } + }; + } +} + +// Node definitions collection +export const FixedNodeDefinitions = [ + { template: Fixed32FromTemplate, executor: new Fixed32FromExecutor() }, + { template: Fixed32FromIntTemplate, executor: new Fixed32FromIntExecutor() }, + { template: Fixed32ToFloatTemplate, executor: new Fixed32ToFloatExecutor() }, + { template: Fixed32ToIntTemplate, executor: new Fixed32ToIntExecutor() }, + { template: Fixed32AddTemplate, executor: new Fixed32AddExecutor() }, + { template: Fixed32SubtractTemplate, executor: new Fixed32SubtractExecutor() }, + { template: Fixed32MultiplyTemplate, executor: new Fixed32MultiplyExecutor() }, + { template: Fixed32DivideTemplate, executor: new Fixed32DivideExecutor() }, + { template: Fixed32NegateTemplate, executor: new Fixed32NegateExecutor() }, + { template: Fixed32AbsTemplate, executor: new Fixed32AbsExecutor() }, + { template: Fixed32SqrtTemplate, executor: new Fixed32SqrtExecutor() }, + { template: Fixed32FloorTemplate, executor: new Fixed32FloorExecutor() }, + { template: Fixed32CeilTemplate, executor: new Fixed32CeilExecutor() }, + { template: Fixed32RoundTemplate, executor: new Fixed32RoundExecutor() }, + { template: Fixed32SignTemplate, executor: new Fixed32SignExecutor() }, + { template: Fixed32MinTemplate, executor: new Fixed32MinExecutor() }, + { template: Fixed32MaxTemplate, executor: new Fixed32MaxExecutor() }, + { template: Fixed32ClampTemplate, executor: new Fixed32ClampExecutor() }, + { template: Fixed32LerpTemplate, executor: new Fixed32LerpExecutor() }, + { template: Fixed32CompareTemplate, executor: new Fixed32CompareExecutor() }, + { template: Fixed32IsZeroTemplate, executor: new Fixed32IsZeroExecutor() }, + { template: Fixed32ConstantsTemplate, executor: new Fixed32ConstantsExecutor() } +]; diff --git a/packages/framework/math/src/nodes/FixedVectorNodes.ts b/packages/framework/math/src/nodes/FixedVectorNodes.ts new file mode 100644 index 00000000..4fd9ca4f --- /dev/null +++ b/packages/framework/math/src/nodes/FixedVectorNodes.ts @@ -0,0 +1,360 @@ +/** + * @zh FixedVector2 定点向量蓝图节点 + * @en FixedVector2 Blueprint Nodes + */ + +import type { BlueprintNodeTemplate, BlueprintNode, INodeExecutor, ExecutionResult } from '@esengine/blueprint'; +import { FixedVector2 } from '../FixedVector2'; +import { Fixed32 } from '../Fixed32'; + +interface FixedVectorContext { + evaluateInput(nodeId: string, pinName: string, defaultValue?: unknown): unknown; +} + +// Make FixedVector2 +export const MakeFixedVector2Template: BlueprintNodeTemplate = { + type: 'MakeFixedVector2', + title: 'Make FixedVector2', + category: 'math', + description: 'Creates FixedVector2 from floats', + keywords: ['make', 'create', 'fixed', 'vector', 'deterministic'], + menuPath: ['Math', 'Fixed Vector', 'Make FixedVector2'], + isPure: true, + inputs: [ + { name: 'x', displayName: 'X', type: 'float', defaultValue: 0 }, + { name: 'y', displayName: 'Y', type: 'float', defaultValue: 0 } + ], + outputs: [ + { name: 'vector', displayName: 'Vector', type: 'object' } + ], + color: '#673AB7' +}; + +export class MakeFixedVector2Executor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedVectorContext; + const x = Number(ctx.evaluateInput(node.id, 'x', 0)); + const y = Number(ctx.evaluateInput(node.id, 'y', 0)); + return { outputs: { vector: FixedVector2.from(x, y) } }; + } +} + +// Break FixedVector2 +export const BreakFixedVector2Template: BlueprintNodeTemplate = { + type: 'BreakFixedVector2', + title: 'Break FixedVector2', + category: 'math', + description: 'Breaks FixedVector2 into X and Y floats', + keywords: ['break', 'split', 'fixed', 'vector'], + menuPath: ['Math', 'Fixed Vector', 'Break FixedVector2'], + isPure: true, + inputs: [ + { name: 'vector', displayName: 'Vector', type: 'object' } + ], + outputs: [ + { name: 'x', displayName: 'X', type: 'float' }, + { name: 'y', displayName: 'Y', type: 'float' } + ], + color: '#673AB7' +}; + +export class BreakFixedVector2Executor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedVectorContext; + const vector = ctx.evaluateInput(node.id, 'vector', FixedVector2.ZERO) as FixedVector2; + const v = vector ?? FixedVector2.ZERO; + return { outputs: { x: v.x.toNumber(), y: v.y.toNumber() } }; + } +} + +// FixedVector2 Add +export const FixedVector2AddTemplate: BlueprintNodeTemplate = { + type: 'FixedVector2Add', + title: 'FixedVector2 +', + category: 'math', + description: 'Adds two fixed vectors', + keywords: ['fixed', 'vector', 'add', '+'], + menuPath: ['Math', 'Fixed Vector', 'Add'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#673AB7' +}; + +export class FixedVector2AddExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedVectorContext; + const a = ctx.evaluateInput(node.id, 'a', FixedVector2.ZERO) as FixedVector2; + const b = ctx.evaluateInput(node.id, 'b', FixedVector2.ZERO) as FixedVector2; + return { outputs: { result: (a ?? FixedVector2.ZERO).add(b ?? FixedVector2.ZERO) } }; + } +} + +// FixedVector2 Subtract +export const FixedVector2SubtractTemplate: BlueprintNodeTemplate = { + type: 'FixedVector2Subtract', + title: 'FixedVector2 -', + category: 'math', + description: 'Subtracts B from A', + keywords: ['fixed', 'vector', 'subtract', '-'], + menuPath: ['Math', 'Fixed Vector', 'Subtract'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#673AB7' +}; + +export class FixedVector2SubtractExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedVectorContext; + const a = ctx.evaluateInput(node.id, 'a', FixedVector2.ZERO) as FixedVector2; + const b = ctx.evaluateInput(node.id, 'b', FixedVector2.ZERO) as FixedVector2; + return { outputs: { result: (a ?? FixedVector2.ZERO).sub(b ?? FixedVector2.ZERO) } }; + } +} + +// FixedVector2 Scale +export const FixedVector2ScaleTemplate: BlueprintNodeTemplate = { + type: 'FixedVector2Scale', + title: 'FixedVector2 *', + category: 'math', + description: 'Scales vector by Fixed32 scalar', + keywords: ['fixed', 'vector', 'scale', '*'], + menuPath: ['Math', 'Fixed Vector', 'Scale'], + isPure: true, + inputs: [ + { name: 'vector', displayName: 'Vector', type: 'object' }, + { name: 'scalar', displayName: 'Scalar', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#673AB7' +}; + +export class FixedVector2ScaleExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedVectorContext; + const vector = ctx.evaluateInput(node.id, 'vector', FixedVector2.ZERO) as FixedVector2; + const scalar = ctx.evaluateInput(node.id, 'scalar', Fixed32.ONE) as Fixed32; + return { outputs: { result: (vector ?? FixedVector2.ZERO).mul(scalar ?? Fixed32.ONE) } }; + } +} + +// FixedVector2 Negate +export const FixedVector2NegateTemplate: BlueprintNodeTemplate = { + type: 'FixedVector2Negate', + title: 'FixedVector2 Negate', + category: 'math', + description: 'Negates a fixed vector', + keywords: ['fixed', 'vector', 'negate', '-'], + menuPath: ['Math', 'Fixed Vector', 'Negate'], + isPure: true, + inputs: [ + { name: 'vector', displayName: 'Vector', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#673AB7' +}; + +export class FixedVector2NegateExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedVectorContext; + const vector = ctx.evaluateInput(node.id, 'vector', FixedVector2.ZERO) as FixedVector2; + return { outputs: { result: (vector ?? FixedVector2.ZERO).neg() } }; + } +} + +// FixedVector2 Length +export const FixedVector2LengthTemplate: BlueprintNodeTemplate = { + type: 'FixedVector2Length', + title: 'FixedVector2 Length', + category: 'math', + description: 'Gets the length of a fixed vector', + keywords: ['fixed', 'vector', 'length', 'magnitude'], + menuPath: ['Math', 'Fixed Vector', 'Length'], + isPure: true, + inputs: [ + { name: 'vector', displayName: 'Vector', type: 'object' } + ], + outputs: [ + { name: 'length', displayName: 'Length', type: 'object' } + ], + color: '#673AB7' +}; + +export class FixedVector2LengthExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedVectorContext; + const vector = ctx.evaluateInput(node.id, 'vector', FixedVector2.ZERO) as FixedVector2; + return { outputs: { length: (vector ?? FixedVector2.ZERO).length() } }; + } +} + +// FixedVector2 Normalize +export const FixedVector2NormalizeTemplate: BlueprintNodeTemplate = { + type: 'FixedVector2Normalize', + title: 'FixedVector2 Normalize', + category: 'math', + description: 'Normalizes a fixed vector', + keywords: ['fixed', 'vector', 'normalize', 'unit'], + menuPath: ['Math', 'Fixed Vector', 'Normalize'], + isPure: true, + inputs: [ + { name: 'vector', displayName: 'Vector', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#673AB7' +}; + +export class FixedVector2NormalizeExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedVectorContext; + const vector = ctx.evaluateInput(node.id, 'vector', FixedVector2.ZERO) as FixedVector2; + return { outputs: { result: (vector ?? FixedVector2.ZERO).normalize() } }; + } +} + +// FixedVector2 Dot +export const FixedVector2DotTemplate: BlueprintNodeTemplate = { + type: 'FixedVector2Dot', + title: 'FixedVector2 Dot', + category: 'math', + description: 'Calculates dot product', + keywords: ['fixed', 'vector', 'dot', 'product'], + menuPath: ['Math', 'Fixed Vector', 'Dot Product'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#673AB7' +}; + +export class FixedVector2DotExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedVectorContext; + const a = ctx.evaluateInput(node.id, 'a', FixedVector2.ZERO) as FixedVector2; + const b = ctx.evaluateInput(node.id, 'b', FixedVector2.ZERO) as FixedVector2; + return { outputs: { result: FixedVector2.dot(a ?? FixedVector2.ZERO, b ?? FixedVector2.ZERO) } }; + } +} + +// FixedVector2 Cross +export const FixedVector2CrossTemplate: BlueprintNodeTemplate = { + type: 'FixedVector2Cross', + title: 'FixedVector2 Cross', + category: 'math', + description: '2D cross product (returns Fixed32)', + keywords: ['fixed', 'vector', 'cross', 'product'], + menuPath: ['Math', 'Fixed Vector', 'Cross Product'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#673AB7' +}; + +export class FixedVector2CrossExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedVectorContext; + const a = ctx.evaluateInput(node.id, 'a', FixedVector2.ZERO) as FixedVector2; + const b = ctx.evaluateInput(node.id, 'b', FixedVector2.ZERO) as FixedVector2; + return { outputs: { result: FixedVector2.cross(a ?? FixedVector2.ZERO, b ?? FixedVector2.ZERO) } }; + } +} + +// FixedVector2 Distance +export const FixedVector2DistanceTemplate: BlueprintNodeTemplate = { + type: 'FixedVector2Distance', + title: 'FixedVector2 Distance', + category: 'math', + description: 'Distance between two points', + keywords: ['fixed', 'vector', 'distance'], + menuPath: ['Math', 'Fixed Vector', 'Distance'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' } + ], + outputs: [ + { name: 'distance', displayName: 'Distance', type: 'object' } + ], + color: '#673AB7' +}; + +export class FixedVector2DistanceExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedVectorContext; + const a = ctx.evaluateInput(node.id, 'a', FixedVector2.ZERO) as FixedVector2; + const b = ctx.evaluateInput(node.id, 'b', FixedVector2.ZERO) as FixedVector2; + return { outputs: { distance: FixedVector2.distance(a ?? FixedVector2.ZERO, b ?? FixedVector2.ZERO) } }; + } +} + +// FixedVector2 Lerp +export const FixedVector2LerpTemplate: BlueprintNodeTemplate = { + type: 'FixedVector2Lerp', + title: 'FixedVector2 Lerp', + category: 'math', + description: 'Linear interpolation between two vectors', + keywords: ['fixed', 'vector', 'lerp', 'interpolate'], + menuPath: ['Math', 'Fixed Vector', 'Lerp'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'object' }, + { name: 'b', displayName: 'B', type: 'object' }, + { name: 't', displayName: 'T', type: 'object' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'object' } + ], + color: '#673AB7' +}; + +export class FixedVector2LerpExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as FixedVectorContext; + const a = ctx.evaluateInput(node.id, 'a', FixedVector2.ZERO) as FixedVector2; + const b = ctx.evaluateInput(node.id, 'b', FixedVector2.ZERO) as FixedVector2; + const t = ctx.evaluateInput(node.id, 't', Fixed32.HALF) as Fixed32; + return { outputs: { result: FixedVector2.lerp(a ?? FixedVector2.ZERO, b ?? FixedVector2.ZERO, t ?? Fixed32.HALF) } }; + } +} + +// Node definitions collection +export const FixedVectorNodeDefinitions = [ + { template: MakeFixedVector2Template, executor: new MakeFixedVector2Executor() }, + { template: BreakFixedVector2Template, executor: new BreakFixedVector2Executor() }, + { template: FixedVector2AddTemplate, executor: new FixedVector2AddExecutor() }, + { template: FixedVector2SubtractTemplate, executor: new FixedVector2SubtractExecutor() }, + { template: FixedVector2ScaleTemplate, executor: new FixedVector2ScaleExecutor() }, + { template: FixedVector2NegateTemplate, executor: new FixedVector2NegateExecutor() }, + { template: FixedVector2LengthTemplate, executor: new FixedVector2LengthExecutor() }, + { template: FixedVector2NormalizeTemplate, executor: new FixedVector2NormalizeExecutor() }, + { template: FixedVector2DotTemplate, executor: new FixedVector2DotExecutor() }, + { template: FixedVector2CrossTemplate, executor: new FixedVector2CrossExecutor() }, + { template: FixedVector2DistanceTemplate, executor: new FixedVector2DistanceExecutor() }, + { template: FixedVector2LerpTemplate, executor: new FixedVector2LerpExecutor() } +]; diff --git a/packages/framework/math/src/nodes/VectorNodes.ts b/packages/framework/math/src/nodes/VectorNodes.ts new file mode 100644 index 00000000..ce028750 --- /dev/null +++ b/packages/framework/math/src/nodes/VectorNodes.ts @@ -0,0 +1,387 @@ +/** + * @zh Vector2 蓝图节点 + * @en Vector2 Blueprint Nodes + */ + +import type { BlueprintNodeTemplate, BlueprintNode, INodeExecutor, ExecutionResult } from '@esengine/blueprint'; +import { Vector2 } from '../Vector2'; + +interface VectorContext { + evaluateInput(nodeId: string, pinName: string, defaultValue?: unknown): unknown; +} + +// Make Vector2 +export const MakeVector2Template: BlueprintNodeTemplate = { + type: 'MakeVector2', + title: 'Make Vector2', + category: 'math', + description: 'Creates a Vector2 from X and Y', + keywords: ['make', 'create', 'vector', 'vector2'], + menuPath: ['Math', 'Vector', 'Make Vector2'], + isPure: true, + inputs: [ + { name: 'x', displayName: 'X', type: 'float', defaultValue: 0 }, + { name: 'y', displayName: 'Y', type: 'float', defaultValue: 0 } + ], + outputs: [ + { name: 'vector', displayName: 'Vector', type: 'vector2' } + ], + color: '#2196F3' +}; + +export class MakeVector2Executor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const x = Number(ctx.evaluateInput(node.id, 'x', 0)); + const y = Number(ctx.evaluateInput(node.id, 'y', 0)); + return { outputs: { vector: new Vector2(x, y) } }; + } +} + +// Break Vector2 +export const BreakVector2Template: BlueprintNodeTemplate = { + type: 'BreakVector2', + title: 'Break Vector2', + category: 'math', + description: 'Breaks a Vector2 into X and Y', + keywords: ['break', 'split', 'vector', 'vector2'], + menuPath: ['Math', 'Vector', 'Break Vector2'], + isPure: true, + inputs: [ + { name: 'vector', displayName: 'Vector', type: 'vector2' } + ], + outputs: [ + { name: 'x', displayName: 'X', type: 'float' }, + { name: 'y', displayName: 'Y', type: 'float' } + ], + color: '#2196F3' +}; + +export class BreakVector2Executor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const vector = ctx.evaluateInput(node.id, 'vector', Vector2.ZERO) as Vector2; + return { outputs: { x: vector?.x ?? 0, y: vector?.y ?? 0 } }; + } +} + +// Vector2 Add +export const Vector2AddTemplate: BlueprintNodeTemplate = { + type: 'Vector2Add', + title: 'Vector2 +', + category: 'math', + description: 'Adds two vectors', + keywords: ['add', 'plus', 'vector'], + menuPath: ['Math', 'Vector', 'Add'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'vector2' }, + { name: 'b', displayName: 'B', type: 'vector2' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'vector2' } + ], + color: '#2196F3' +}; + +export class Vector2AddExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const a = ctx.evaluateInput(node.id, 'a', Vector2.ZERO) as Vector2; + const b = ctx.evaluateInput(node.id, 'b', Vector2.ZERO) as Vector2; + return { outputs: { result: Vector2.add(a ?? Vector2.ZERO, b ?? Vector2.ZERO) } }; + } +} + +// Vector2 Subtract +export const Vector2SubtractTemplate: BlueprintNodeTemplate = { + type: 'Vector2Subtract', + title: 'Vector2 -', + category: 'math', + description: 'Subtracts B from A', + keywords: ['subtract', 'minus', 'vector'], + menuPath: ['Math', 'Vector', 'Subtract'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'vector2' }, + { name: 'b', displayName: 'B', type: 'vector2' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'vector2' } + ], + color: '#2196F3' +}; + +export class Vector2SubtractExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const a = ctx.evaluateInput(node.id, 'a', Vector2.ZERO) as Vector2; + const b = ctx.evaluateInput(node.id, 'b', Vector2.ZERO) as Vector2; + return { outputs: { result: Vector2.subtract(a ?? Vector2.ZERO, b ?? Vector2.ZERO) } }; + } +} + +// Vector2 Scale +export const Vector2ScaleTemplate: BlueprintNodeTemplate = { + type: 'Vector2Scale', + title: 'Vector2 *', + category: 'math', + description: 'Scales a vector by a scalar', + keywords: ['scale', 'multiply', 'vector'], + menuPath: ['Math', 'Vector', 'Scale'], + isPure: true, + inputs: [ + { name: 'vector', displayName: 'Vector', type: 'vector2' }, + { name: 'scalar', displayName: 'Scalar', type: 'float', defaultValue: 1 } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'vector2' } + ], + color: '#2196F3' +}; + +export class Vector2ScaleExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const vector = ctx.evaluateInput(node.id, 'vector', Vector2.ZERO) as Vector2; + const scalar = Number(ctx.evaluateInput(node.id, 'scalar', 1)); + return { outputs: { result: Vector2.multiply(vector ?? Vector2.ZERO, scalar) } }; + } +} + +// Vector2 Length +export const Vector2LengthTemplate: BlueprintNodeTemplate = { + type: 'Vector2Length', + title: 'Vector2 Length', + category: 'math', + description: 'Gets the length of a vector', + keywords: ['length', 'magnitude', 'vector'], + menuPath: ['Math', 'Vector', 'Length'], + isPure: true, + inputs: [ + { name: 'vector', displayName: 'Vector', type: 'vector2' } + ], + outputs: [ + { name: 'length', displayName: 'Length', type: 'float' } + ], + color: '#2196F3' +}; + +export class Vector2LengthExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const vector = ctx.evaluateInput(node.id, 'vector', Vector2.ZERO) as Vector2; + return { outputs: { length: (vector ?? Vector2.ZERO).length } }; + } +} + +// Vector2 Normalize +export const Vector2NormalizeTemplate: BlueprintNodeTemplate = { + type: 'Vector2Normalize', + title: 'Vector2 Normalize', + category: 'math', + description: 'Normalizes a vector to unit length', + keywords: ['normalize', 'unit', 'vector'], + menuPath: ['Math', 'Vector', 'Normalize'], + isPure: true, + inputs: [ + { name: 'vector', displayName: 'Vector', type: 'vector2' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'vector2' } + ], + color: '#2196F3' +}; + +export class Vector2NormalizeExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const vector = ctx.evaluateInput(node.id, 'vector', Vector2.ZERO) as Vector2; + return { outputs: { result: (vector ?? Vector2.ZERO).normalized() } }; + } +} + +// Vector2 Dot +export const Vector2DotTemplate: BlueprintNodeTemplate = { + type: 'Vector2Dot', + title: 'Vector2 Dot', + category: 'math', + description: 'Calculates dot product', + keywords: ['dot', 'product', 'vector'], + menuPath: ['Math', 'Vector', 'Dot Product'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'vector2' }, + { name: 'b', displayName: 'B', type: 'vector2' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'float' } + ], + color: '#2196F3' +}; + +export class Vector2DotExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const a = ctx.evaluateInput(node.id, 'a', Vector2.ZERO) as Vector2; + const b = ctx.evaluateInput(node.id, 'b', Vector2.ZERO) as Vector2; + return { outputs: { result: Vector2.dot(a ?? Vector2.ZERO, b ?? Vector2.ZERO) } }; + } +} + +// Vector2 Cross +export const Vector2CrossTemplate: BlueprintNodeTemplate = { + type: 'Vector2Cross', + title: 'Vector2 Cross', + category: 'math', + description: '2D cross product (returns scalar)', + keywords: ['cross', 'product', 'vector'], + menuPath: ['Math', 'Vector', 'Cross Product'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'vector2' }, + { name: 'b', displayName: 'B', type: 'vector2' } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'float' } + ], + color: '#2196F3' +}; + +export class Vector2CrossExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const a = ctx.evaluateInput(node.id, 'a', Vector2.ZERO) as Vector2; + const b = ctx.evaluateInput(node.id, 'b', Vector2.ZERO) as Vector2; + return { outputs: { result: Vector2.cross(a ?? Vector2.ZERO, b ?? Vector2.ZERO) } }; + } +} + +// Vector2 Distance +export const Vector2DistanceTemplate: BlueprintNodeTemplate = { + type: 'Vector2Distance', + title: 'Vector2 Distance', + category: 'math', + description: 'Distance between two points', + keywords: ['distance', 'length', 'vector'], + menuPath: ['Math', 'Vector', 'Distance'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'vector2' }, + { name: 'b', displayName: 'B', type: 'vector2' } + ], + outputs: [ + { name: 'distance', displayName: 'Distance', type: 'float' } + ], + color: '#2196F3' +}; + +export class Vector2DistanceExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const a = ctx.evaluateInput(node.id, 'a', Vector2.ZERO) as Vector2; + const b = ctx.evaluateInput(node.id, 'b', Vector2.ZERO) as Vector2; + return { outputs: { distance: Vector2.distance(a ?? Vector2.ZERO, b ?? Vector2.ZERO) } }; + } +} + +// Vector2 Lerp +export const Vector2LerpTemplate: BlueprintNodeTemplate = { + type: 'Vector2Lerp', + title: 'Vector2 Lerp', + category: 'math', + description: 'Linear interpolation between two vectors', + keywords: ['lerp', 'interpolate', 'vector'], + menuPath: ['Math', 'Vector', 'Lerp'], + isPure: true, + inputs: [ + { name: 'a', displayName: 'A', type: 'vector2' }, + { name: 'b', displayName: 'B', type: 'vector2' }, + { name: 't', displayName: 'T', type: 'float', defaultValue: 0.5 } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'vector2' } + ], + color: '#2196F3' +}; + +export class Vector2LerpExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const a = ctx.evaluateInput(node.id, 'a', Vector2.ZERO) as Vector2; + const b = ctx.evaluateInput(node.id, 'b', Vector2.ZERO) as Vector2; + const t = Number(ctx.evaluateInput(node.id, 't', 0.5)); + return { outputs: { result: Vector2.lerp(a ?? Vector2.ZERO, b ?? Vector2.ZERO, t) } }; + } +} + +// Vector2 Rotate +export const Vector2RotateTemplate: BlueprintNodeTemplate = { + type: 'Vector2Rotate', + title: 'Vector2 Rotate', + category: 'math', + description: 'Rotates a vector by angle (radians)', + keywords: ['rotate', 'turn', 'vector'], + menuPath: ['Math', 'Vector', 'Rotate'], + isPure: true, + inputs: [ + { name: 'vector', displayName: 'Vector', type: 'vector2' }, + { name: 'angle', displayName: 'Angle (rad)', type: 'float', defaultValue: 0 } + ], + outputs: [ + { name: 'result', displayName: 'Result', type: 'vector2' } + ], + color: '#2196F3' +}; + +export class Vector2RotateExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const vector = ctx.evaluateInput(node.id, 'vector', Vector2.ZERO) as Vector2; + const angle = Number(ctx.evaluateInput(node.id, 'angle', 0)); + return { outputs: { result: (vector ?? Vector2.ZERO).rotated(angle) } }; + } +} + +// Vector2 From Angle +export const Vector2FromAngleTemplate: BlueprintNodeTemplate = { + type: 'Vector2FromAngle', + title: 'Vector2 From Angle', + category: 'math', + description: 'Creates unit vector from angle (radians)', + keywords: ['from', 'angle', 'direction', 'vector'], + menuPath: ['Math', 'Vector', 'From Angle'], + isPure: true, + inputs: [ + { name: 'angle', displayName: 'Angle (rad)', type: 'float', defaultValue: 0 } + ], + outputs: [ + { name: 'vector', displayName: 'Vector', type: 'vector2' } + ], + color: '#2196F3' +}; + +export class Vector2FromAngleExecutor implements INodeExecutor { + execute(node: BlueprintNode, context: unknown): ExecutionResult { + const ctx = context as VectorContext; + const angle = Number(ctx.evaluateInput(node.id, 'angle', 0)); + return { outputs: { vector: Vector2.fromAngle(angle) } }; + } +} + +// Node definitions collection +export const VectorNodeDefinitions = [ + { template: MakeVector2Template, executor: new MakeVector2Executor() }, + { template: BreakVector2Template, executor: new BreakVector2Executor() }, + { template: Vector2AddTemplate, executor: new Vector2AddExecutor() }, + { template: Vector2SubtractTemplate, executor: new Vector2SubtractExecutor() }, + { template: Vector2ScaleTemplate, executor: new Vector2ScaleExecutor() }, + { template: Vector2LengthTemplate, executor: new Vector2LengthExecutor() }, + { template: Vector2NormalizeTemplate, executor: new Vector2NormalizeExecutor() }, + { template: Vector2DotTemplate, executor: new Vector2DotExecutor() }, + { template: Vector2CrossTemplate, executor: new Vector2CrossExecutor() }, + { template: Vector2DistanceTemplate, executor: new Vector2DistanceExecutor() }, + { template: Vector2LerpTemplate, executor: new Vector2LerpExecutor() }, + { template: Vector2RotateTemplate, executor: new Vector2RotateExecutor() }, + { template: Vector2FromAngleTemplate, executor: new Vector2FromAngleExecutor() } +]; diff --git a/packages/framework/math/src/nodes/index.ts b/packages/framework/math/src/nodes/index.ts new file mode 100644 index 00000000..29bbca7d --- /dev/null +++ b/packages/framework/math/src/nodes/index.ts @@ -0,0 +1,29 @@ +/** + * @zh 数学库蓝图节点 + * @en Math Library Blueprint Nodes + * + * @zh 导出所有数学相关的蓝图节点 + * @en Exports all math-related blueprint nodes + */ + +export * from './VectorNodes'; +export * from './FixedNodes'; +export * from './FixedVectorNodes'; +export * from './ColorNodes'; + +// Re-export node definition collections +import { VectorNodeDefinitions } from './VectorNodes'; +import { FixedNodeDefinitions } from './FixedNodes'; +import { FixedVectorNodeDefinitions } from './FixedVectorNodes'; +import { ColorNodeDefinitions } from './ColorNodes'; + +/** + * @zh 所有数学库蓝图节点定义 + * @en All math library blueprint node definitions + */ +export const MathNodeDefinitions = [ + ...VectorNodeDefinitions, + ...FixedNodeDefinitions, + ...FixedVectorNodeDefinitions, + ...ColorNodeDefinitions +]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e2715856..9569613c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1575,6 +1575,10 @@ importers: version: 5.9.3 packages/framework/math: + dependencies: + '@esengine/blueprint': + specifier: workspace:* + version: link:../blueprint devDependencies: '@rollup/plugin-commonjs': specifier: ^28.0.3