CGA.js 引擎架构文档

CGA.js Engine Architecture

基于 ANTLR4 + TypeScript 的 CityEngine CGA 规则解析与三维生成引擎的分层设计与开发指南

ANTLR4 Parser Three.js Geometry 302 Parser Tests 53 Function Tests Beta ↔ Prod 隔离

📐 架构总览

CGA.js 引擎是一个分层架构的 CGA 规则解析与三维生成系统,核心流程为:源代码 → 解析 → AST → 求值 → 几何生成

CGA Source
Lexer
Parser
AST
Evaluator
Shapes

技术栈

层级技术说明
ParserANTLR4 + TypeScriptLL(*) 解析器,自动生成 Lexer/Parser/Visitor
ASTTypeScript Interfaces强类型抽象语法树
EvaluatorTypeScript递归下降求值 + 函数注册表
GeometryThree.js (BufferGeometry)3D 网格生成与变换
BuildViteESM/CJS 双格式输出

⚙️ 解析管线(Pipeline)

从 CGA 源码到三维模型的完整处理流程:

1
预处理(Preprocess)

normalizeBareStochasticBranches() + preprocessCityEngineSyntax()
处理缩进式随机分支、CityEngine 扩展语法转换

2
词法分析(Lexical Analysis)

CharStreams → CGAGrammarLexer → CommonTokenStream
将源码转换为 Token 序列(IDENT、FLOAT、STRING、关键字等)

3
语法分析(Syntax Analysis)

CGAGrammarParser → parse tree
ANTLR4 根据 Grammar 规则生成解析树,同时收集 Ambiguity 警告

4
AST 构建(AST Building)

ASTBuilder (Visitor) → CGAScript AST
遍历解析树,构建类型化的抽象语法树

5
求值执行(Evaluation)

evaluate() → Shape[]
递归求值 AST,调用几何函数,生成三维 Shape 树

6
序列化输出(Serialization)

Shape → JSON / OBJ / GLB / STL
将几何数据导出为前端可渲染的格式

🏗️ 分层设计

🟦 Parser 层 — 语法解析

负责将 CGA 源码转换为结构化 AST。核心文件包括 Grammar 定义、ANTLR 生成器和 AST Builder。

grammar/CGAGrammar.g4 → src/parser/antlr/* → src/parser/visitors/ast-builder.ts → src/parser/ast/types.ts
🟩 Evaluator 层 — 规则求值

遍历 AST,执行 operation 链,管理递归深度、变量作用域和随机种子。通过函数注册表分派到具体实现。

src/runtime/evaluator.ts → src/runtime/function-handlers.ts → src/runtime/expression-evaluator.ts
🟪 Geometry 层 — 三维计算

纯函数集合,接收 Shape 输入,返回变换后的 Shape。包括基本体创建、布尔运算、UV 投影、屋顶生成等。

src/geometry/operations/create/* → src/geometry/operations/split/* → src/geometry/operations/transform/*

🔍 Parser 层详解

Grammar 结构

Grammar 采用 ANTLR4 格式,核心规则包括:

关键设计:通用 Block 解析

核心原则: blockOperation 是一个 catch-all 规则,匹配所有 ident(args){body} 形式的代码。 这意味着 setbackoffsetinline 以及未来的新 block 函数,都不需要修改 Grammar
// Grammar 中的 blockOperation(通用规则)
blockOperation
    : ident ('(' argList? ')')? blockBody (operationSuffix? blockBody)*
    ;

// 以下都被解析为同一个 BlockOperation AST 节点:
setback(1) { front: A | back: B }
offset(1) { inside: A | border: B }
inline("recompose") { A } { B }
myNewFunc(10) { body }  // ← 未来新函数,自动兼容

Parser 回归测试

216 个测试覆盖所有 operation 类型、表达式类型和边缘 case。任何 Grammar 修改都必须通过这些测试。

测试组数量覆盖
Basic Structures11空文件、多规则、声明、注解、注释
Simple Operations60+所有内置函数解析
Block / Comp / Split35块操作、组件选择、分割轴
Expressions40运算符、数组、索引、成员访问
Complex Nesting8多层嵌套链
Edge / Error Cases17边界值、错误检测

🧮 Evaluator 层详解

求值上下文(EvalContext)

每个编译请求创建一个独立的求值上下文,包含:

Operation 分派

Evaluator 根据 AST 节点类型分派到对应处理器:

evalOperation(op, shape, ctx)
  ├─ SimpleOperation → evalSimpleOperation(name, args, shape, ctx)
  ├─ CompOperation   → evalCompOperation(selector, branches, shape, ctx)
  ├─ SplitOperation  → evalSplitOperation(axis, branches, shape, ctx)
  ├─ PushPopOperation→ evalRuleBody(body, shape, ctx)
  ├─ BlockOperation  → evalBlockOperation(op, shape, ctx)
  └─ TempShapeOperation → evalRuleBody(body, shape, ctx)

📐 Geometry 层详解

Geometry 层是纯函数集合,按功能分为多个目录:

目录函数说明
create/extrude, roof*, primitive*, insert, scatter, taper创建新几何体
split/comp, setback, offset, splitArea, innerRectangle, shapeL/U/O面/边分割与退缩
transform/alignScope*, mirrorScope, rotateScope, setPivot坐标系变换
material/color, texture, setupProjection, projectUV, *UV材质与 UV
boolean/union, subtract, intersect布尔运算
manipulate/cleanupGeometry, deleteHoles, softenNormals, rectify几何清理
scope/t, s, r, center, mirrorScope 变换

📋 函数注册表(Function Registry)

为了避免 evaluator.ts 成为"千行怪兽文件",引擎引入了函数注册表机制。

注册 API

// src/runtime/evaluator.ts
export function registerSimpleFunction(
  name: string,
  handler: (shape, argVals, ctx) => Shape[]
);

export function registerBlockFunction(
  name: string,
  handler: (op, shape, argVals, ctx) => Shape[]
);

工作原理

extrude(10) 查表 extrude handler src/geometry/operations/create/extrude.ts
setback(1){A} 查表 setback handler src/geometry/operations/split/setback.ts
color("#f00") 查表 color handler src/geometry/operations/material/material.ts
myNewFunc() 查表 自定义 handler 外部注册

现有内置函数注册位置

目前内置函数仍在 evaluator.ts 的 switch-case 中,但注册表优先于 switch-case。未来新函数建议通过注册表添加。

➕ 添加新函数指南

场景 A:普通函数调用 myFunc(args)

  1. 实现几何逻辑
    // src/geometry/operations/create/my-func.ts
    export function myFunc(shape: Shape, height: number): Shape {
      // 纯函数:输入 Shape,返回新 Shape
      return newShape;
    }
  2. 注册到引擎
    import { registerSimpleFunction } from '../runtime/evaluator.js';
    import { myFunc } from '../geometry/operations/create/my-func.js';
    
    registerSimpleFunction('myFunc', (shape, args, ctx) => {
      return [myFunc(shape, args[0] as number)];
    });
  3. 添加测试
    // tests/parser/operations.test.ts
    { name: 'myFunc', code: 'Lot --> myFunc(10)' }
    
    // tests/functions/my-func.test.ts
    // 验证执行结果

场景 B:Block 形式函数 myFunc(args){body}

  1. 实现几何逻辑(同上)
  2. 注册 Block Handler
    import { registerBlockFunction } from '../runtime/evaluator.js';
    
    registerBlockFunction('myFunc', (op, shape, argVals, ctx) => {
      // op.bodies: RuleBody[] — block 内的规则体
      const result = myFuncGeometry(shape, argVals[0] as number);
      if (op.bodies.length > 0) {
        return evalRuleBody(op.bodies[0], result, ctx);
      }
      return [result];
    });
  3. 添加测试(同上)
注意: 以上两种场景都不需要修改 Grammar。Parser 会自动将 myFunc(args) 解析为 SimpleOperation,将 myFunc(args){body} 解析为 BlockOperation。

📜 Grammar 修改指南

什么时候改 Grammar?

场景是否改 Grammar替代方案
增加新函数不改函数注册表
Block 形式新函数不改BlockOperation 通用解析 + 注册表
修复语法 bug可以改必须跑完全量回归测试
全新语法结构谨慎评估先考虑 AST 变换层或预处理

修改 Grammar 的强制流程

  1. 备份cp -r dist/ dist-backup-xxx/
  2. 修改grammar/CGAGrammar.g4
  3. 重新生成npm run generate-parser
  4. 更新 AST Buildersrc/parser/ast/types.ts + src/parser/visitors/ast-builder.ts
  5. 运行回归测试npm test(216+ 测试必须全过)
  6. 检查 Ambiguity — 观察测试输出中的 [Parser Ambiguity] 警告
  7. 运行函数测试npm run test:functions
  8. 构建npm run build
  9. Beta 部署./scripts/deploy.sh beta
  10. 手动验证 — 在 beta.cgajs.com 测试
  11. 生产部署./scripts/deploy.sh prod 1.3.0

为什么尽量不改 Grammar?

真实案例:splitAndSetbackPerimeter 的教训

尝试在 Grammar 中添加 saspOperation 规则以支持 splitAndSetbackPerimeter 的特殊语法:

saspOperation
    : 'splitAndSetbackPerimeter' '(' expression ')' '{' saspBranch '}' '{' saspRemainder '}'

该规则的 FIRST 集合与 blockOperation 完全重叠,导致 compsplitsetbackoffsetinline 等大量函数的解析出错。

正确做法:利用已有的 blockOperation 通用解析,在 Evaluator 层识别函数名并做特殊处理。

🚀 部署架构

Beta ↔ Prod 隔离

引擎采用双环境隔离部署策略:

环境域名引擎目录前端目录用途
Betabeta.cgajs.com/www/wwwroot/beta-engine//www/wwwroot/beta.cgajs.com/新功能验证、回归测试
Prodwww.cgajs.com/www/wwwroot/cgajs-engine//www/wwwroot/www.cgajs.com/正式用户访问

API 根据请求的 Host Header 路由到对应引擎:

if host contains "beta":
    cli = "/www/wwwroot/beta-engine/cli-wrapper.cjs"
else:
    cli = "/www/wwwroot/cgajs-engine/cli-wrapper.cjs"

部署流程(前端+引擎同步)

1
开发 & 本地测试

npm test + npm run test:functions

2
Beta 构建

Admin 后台"构建 Beta" 或 ./scripts/deploy.sh beta

自动更新 package.json 版本号,约 20-30 秒完成,后台自动轮询检测

3
Beta 验证

访问 beta.cgajs.com/ide.html,测试 CGA + 前端交互

4
晋升生产

Admin 后台"发布到生产" 或 ./scripts/deploy.sh prod 1.3.0

自动同步:引擎(dist) + 前端(ide.html, enhance.js, ku.html, ku/)

5
紧急回滚

如发现问题,Admin 后台一键"紧急回滚"

同时回滚引擎 + 前端文件到发布前状态

⚠️ 注意:Admin 后台的"发布到生产"已自动同步前端文件(ide.html, enhance.js, ku.html, ku/)。
不再需要在 beta 和 www 之间手动复制前端文件。

🧪 测试体系

三层测试金字塔

层级文件数量目的
Parser Unittests/parser/operations.test.ts216验证所有语法构造能被正确解析
Functiontests/functions/*.test.ts53 cases验证每个函数的几何输出正确
E2Etests/e2e/*.test.ts10+验证完整 CGA 规则链的执行

部署脚本中的强制检查

scripts/deploy.sh 在部署前自动运行 Parser 回归测试和函数测试,任何失败都会阻断部署:

Step 1/4: Running parser regression tests... ✅
Step 2/4: Running function tests... ✅
Step 3/4: Building engine... ✅
Step 4/4: Deploying to beta/prod... ✅

📋 标准工作流速查

添加新函数(不改 Grammar)

1. geometry/operations/[category]/my-func.ts    ← 几何实现
2. registerSimpleFunction('myFunc', handler)     ← 注册
3. tests/parser/operations.test.ts              ← Parser 测试
4. tests/functions/ 或 tests/e2e/               ← 功能测试
5. npm test + npm run test:functions            ← 验证
6. ./scripts/deploy.sh beta                     ← Beta 部署
7. beta.cgajs.com 手动验证                      ← 确认
8. ./scripts/deploy.sh prod 1.3.0               ← 生产部署

修复 Parser Bug(需要改 Grammar)

1. 修改 grammar/CGAGrammar.g4
2. npm run generate-parser
3. 更新 src/parser/ast/types.ts
4. 更新 src/parser/visitors/ast-builder.ts
5. npm test(必须 216/216 通过)
6. 检查 Ambiguity 警告
7. npm run test:functions
8. npm run build
9. ./scripts/deploy.sh beta
10. beta.cgajs.com 验证
11. ./scripts/deploy.sh prod 1.3.0
一句话总结: 加函数 → 注册表 + 几何层。改 Grammar → 全量测试 + Beta 验证。能不改 Grammar 就不改。

📊 CGA高频函数速查表 — Top 100

本表基于 CityEngine 官方 CGA 函数库与 marketplace.cgajs.com 的实际使用统计,整理了建筑规则生成中最常调用的 100+ 个高频函数。点击函数名可跳转至详细文档。

✅ 已实现 ⚠️ 部分实现 ❌ 未实现

1. 几何操作类(Top 30)

函数说明常用参数状态
extrude拉伸,最核心操作extrude(10)
split分割,建筑立面必备split(x) { ~1: A }*
comp(f)面组件分割comp(f) { top: Roof }
comp(e)边组件分割comp(e) { all: Edge }
translate平移t(0,5,0)
rotate旋转r(0,90,0)
scale缩放s(2,1,2)
color设置颜色color("#ff6600")
primitiveCube立方体图元primitiveCube()
texture贴图texture("wall.jpg")
roofGable人字屋顶roofGable(30)
roofHip四坡屋顶roofHip(30)
setback退界setback(2)
offset偏移offset(-0.3)
mirror镜像mirrorScope(x)
center居中center(xz)
taper锥化taper(0.8)
envelope包络envelope(0.5, 0.3)
insert插入资产insert("door.obj")
primitiveSphere球体图元primitiveSphere()
primitiveCylinder圆柱图元primitiveCylinder()
primitiveCone圆锥图元primitiveCone()
roofPyramid金字塔屋顶roofPyramid(30)
roofShed单坡屋顶roofShed(15)
setbackPerEdge按边退界setbackPerEdge(1)⚠️
innerRectangle内接矩形innerRectangle()
shapeLL形分割shapeL(2,2)
shapeUU形分割shapeU(2,2)
convexify凸化convexify()
deleteHoles删除孔洞deleteHoles()
// 典型用法:建筑主体 → 立面分割 → 屋顶
Lot -->
  extrude(rand(10,20))
  comp(f) { top: Roof | side: Facade }

Facade -->
  split(y) { ~1: Floor }*

Roof -->
  roofGable(30)
  texture("roof.jpg")

2. 变换与Scope类(Top 15)

函数说明常用参数状态
alignScopeToAxes对齐坐标轴alignScopeToAxes(y)
alignScopeToGeometry对齐几何体alignScopeToGeometry(zUp,0)
setPivot设置枢轴点setPivot(xyz,0,0,0)
rotateScope旋转作用域rotateScope(0,45,0)
mirrorScope镜像作用域mirrorScope(x)
scope.sx作用域X尺寸scope.sx
scope.sy作用域Y尺寸scope.sy
scope.sz作用域Z尺寸scope.sz
scope.tx作用域X位置scope.tx
scope.ty作用域Y位置scope.ty
scope.tz作用域Z位置scope.tz
// 基于Scope的自适应缩放
Adaptive -->
  alignScopeToAxes(y)
  s(scope.sx * 0.9, scope.sy, scope.sz * 0.9)
  center(xz)

3. 材质与UV类(Top 10)

函数说明常用参数状态
setMaterial设置材质setMaterial("glass", color, 0.8)
resetMaterial重置材质resetMaterial()
setupProjection设置投影setupProjection(0, scope.sx, scope.sy)
projectUV投影UVprojectUV(0)
tileUV平铺UVtileUV(0, 2, 2)
rotateUV旋转UVrotateUV(0, 45)
scaleUV缩放UVscaleUV(0, 2, 1)
// 砖墙材质UV设置
BrickWall -->
  setupProjection(0, scope.sx, scope.sy)
  projectUV(0)
  tileUV(0, scope.sx / 0.5, scope.sy / 0.3)
  texture("brick.jpg")

4. 数学函数类(Top 15)

函数说明常用参数状态
abs绝对值abs(-5)
rand随机数rand(0,1)
min最小值min(a, b)
max最大值max(a, b, c)
clamp钳制clamp(val, 0, 10)
sqrt平方根sqrt(16)
pow幂运算pow(2, 3)
sin正弦sin(30)
cos余弦cos(45)
tan正切tan(60)
floor向下取整floor(3.7)
ceil向上取整ceil(3.2)
round四舍五入round(3.5)
log自然对数log(100)
exp指数exp(1)
// 随机楼层高度
FloorHeight -->
  floor(rand(3.0, 4.5) * 10) / 10

5. 字符串与列表类(Top 10)

函数说明常用参数状态
strlen字符串长度strlen("hello")
search搜索子串find("abc", "b")
replace替换子串replace("a,b", ",", ";")
split字符串分割splitString("a;b;c", ";")
listSize列表大小listSize("a;b;")
listAdd列表添加listAdd("a;b;", "c")
listRandom随机选择listRandom("red;blue;green;")
listItem获取列表项listItem("a;b;c;", 1)
append追加元素append([1,2], 3)
reverse反转数组reverse([1,2,3])
// 从列表随机选材质
PickMaterial -->
  listRandom("brick.jpg;concrete.jpg;wood.jpg;")
  texture(listRandom("brick.jpg;concrete.jpg;wood.jpg;"))

6. 几何查询类(Top 15)

函数说明常用参数状态
geometry.area面积geometry.area
geometry.height高度geometry.height
geometry.volume体积geometry.volume
geometry.nFaces面数geometry.nFaces
geometry.nVertices顶点数geometry.nVertices
geometry.isPlanar是否平面geometry.isPlanar
geometry.isClosedSurface是否封闭geometry.isClosedSurface
geometry.boundaryLength边界长度geometry.boundaryLength
geometry.angle角度geometry.angle⚠️
geometry.bbArea包围盒面积geometry.bbArea
geometry.top顶部Y坐标geometry.top
geometry.bottom底部Y坐标geometry.bottom
// 按面积自适应细节
Detail -->
  case geometry.area > 50 : HighDetail
  else : LowDetail

7. 资产与文件类(Top 10)

函数说明常用参数状态
assetInfo资产信息assetInfo("tree.obj", "sx")
assetFitSize资产适配尺寸assetFitSize(assets, scope.sx)
assetApproxRatio资产近似比例assetApproxRatio(assets)
fileSearch文件搜索fileSearch("*.jpg")
fileExists文件存在检查fileExists("data.txt")
fileBasename文件基名fileBasename("a/b.jpg")
// 动态加载适配窗户
Window -->
  insert(assetFitSize(assets, scope.sx * 0.8))

8. 布尔与高级操作类(Top 8)

函数说明常用参数状态
union并集union { A | B }⚠️
subtract差集subtract { A | B }⚠️
intersect交集intersect { A | B }⚠️
scatter散布scatter(surface, 20)
repeat重复split(x) { ~1: A }*⚠️
copy复制copy(5)
instance实例化instance("ref")
label标签label("facade")
setTag设置标签setTag("window")
deleteTags删除标签deleteTags()
// 散布树木到地面
Ground -->
  scatter(surface, 50)
  insert("tree.obj")

9. 颜色与转换类(Top 3)

函数说明常用参数状态
colorRamp颜色渐变colorRamp("height", 0.5)
rgbRGB颜色rgb(1,0.5,0)
hsbHSB颜色hsb(120,0.8,0.9)
// 高度驱动的颜色渐变
ColorByHeight -->
  color(colorRamp("height", geometry.height / 50))
说明:实现状态基于 CGA.js 引擎当前版本(v1.3.x)。部分函数存在别名映射(如 translatetloglnroundrint),引擎内部均已支持。⚠️ 标记的函数为简化实现或仅特定上下文可用。