bplinejs 0.0.102 → 0.0.103-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Material/Material.js +2 -2
- package/lib/Material/Material.js.map +1 -1
- package/lib/Render/DevelopmentValidator.js +12 -3
- package/lib/Render/DevelopmentValidator.js.map +1 -1
- package/lib/Render/PipelineManager.js +4 -0
- package/lib/Render/PipelineManager.js.map +1 -1
- package/lib/Text/font.d.ts +53 -0
- package/lib/Text/font.js +110 -0
- package/lib/Text/font.js.map +1 -0
- package/lib/Text/index.d.ts +164 -0
- package/lib/Text/index.js +333 -0
- package/lib/Text/index.js.map +1 -0
- package/lib/global-types.d.ts +2 -1
- package/lib/index.d.ts +4 -1
- package/lib/index.js +3 -1
- package/package.json +6 -2
- package/releases/0.0.102.md +2 -0
package/lib/Material/Material.js
CHANGED
|
@@ -237,7 +237,7 @@ var Material = class {
|
|
|
237
237
|
addGeometryType(type) {
|
|
238
238
|
let tag;
|
|
239
239
|
if (type === "Rect2D") tag = "rect";
|
|
240
|
-
else if (type === "Poly2D") tag = "poly";
|
|
240
|
+
else if (type === "Poly2D" || type === "Text") tag = "poly";
|
|
241
241
|
else if (type === "NGon2D") tag = "ngon";
|
|
242
242
|
else tag = type;
|
|
243
243
|
if (!this._geometryTypes.has(tag)) {
|
|
@@ -258,7 +258,7 @@ var Material = class {
|
|
|
258
258
|
if (key === void 0) {
|
|
259
259
|
let tag = type;
|
|
260
260
|
if (type === "Rect2D") tag = "rect";
|
|
261
|
-
else if (type === "Poly2D") tag = "poly";
|
|
261
|
+
else if (type === "Poly2D" || type === "Text") tag = "poly";
|
|
262
262
|
else if (type === "NGon2D") tag = "ngon";
|
|
263
263
|
key = this.key + "_" + tag;
|
|
264
264
|
this._pipelineKeys.set(type, key);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Material.js","names":[],"sources":["../../src/scripts/Material/Material.ts"],"sourcesContent":["/// <reference types=\"@webgpu/types\" preserve=\"true\" />\nimport { GETID } from \"../ID\";\nimport Style from \"../Style\";\nimport type { StyleChange, StyleSubscriber, BorderAlign, PixelAligned } from \"../Style\";\ninterface MaterialBuffersLike {\n uniform?: number;\n texture?: number;\n sampler?: number;\n}\ninterface ShadersLike {\n ngonVertexShader?: string;\n ngonFragmentShader?: string;\n polyVertexShader?: string;\n polyFragmentShader?: string;\n rectVertexShader?: string;\n rectFragmentShader?: string;\n}\n/** 通知类别不是独立版本;材质仍只有一个 version。 */\ntype MaterialChangeKind = \"all\" | \"style\" | \"pipeline\" | \"texture\" | \"textureData\" | \"sampler\";\n/** 材质变化影响:样式数组、场景资源引用分别处理。 */\ninterface MaterialChange {\n readonly source: MaterialLike;\n readonly style: boolean;\n readonly resources: boolean;\n}\n/** 材质消费者,回调只安排下一帧工作。 */\ninterface MaterialSubscriber {\n /**\n * 接收材质变化通知,区分样式数据与资源引用的更新。\n * @param change 变化来源、区域及字段信息\n * @example\n * materialSubscriber.onMaterialChange(change);\n * @returns 无返回值。\n */\n onMaterialChange(change: MaterialChange): void;\n}\ninterface MaterialLike extends StyleSubscriber {\n readonly id: number;\n readonly type: string;\n readonly key: string;\n readonly version: number;\n style: Style;\n transparent: boolean;\n depthTest: boolean;\n depthWrite: boolean;\n cullMode: GPUCullMode;\n /**\n * 追加已使用的几何类型标记;Rect2D 对应 rect,重复绑定不递增版本。\n * @param type Geometry.type\n * @example\n * material.addGeometryType(type);\n * @returns 无返回值\n */\n addGeometryType(type: string): void;\n /**\n * 获取指定几何类型的管线缓存键。\n * 材质 key 描述共享状态,类型后缀区分同一材质的不同几何模板。\n * @param type Geometry.type\n * @example\n * material.getPipelineKey(type);\n * @returns 当前几何对应的管线键\n */\n getPipelineKey(type: string): string;\n rectVertexShader: string | undefined;\n rectFragmentShader: string | undefined;\n polyVertexShader: string | undefined;\n polyFragmentShader: string | undefined;\n ngonVertexShader: string | undefined;\n ngonFragmentShader: string | undefined;\n /**\n * 原生线开关决定是否需要附加 line-list 管线,其余分区通过 Uniform 控制。\n * @example\n * material.updateKey();\n * @returns 无返回值。\n */\n updateKey(): void;\n /**\n * 递增单一版本并通知消费者,颜色变化不会改变 key。\n * @param kind 影响范围,手动调用默认全部失效\n * @example\n * material.updateVersion(kind);\n * @returns 无返回值。\n */\n updateVersion(kind?: MaterialChangeKind): void;\n /**\n * 关联使用当前材质的消费者,Set 自动去重。\n * @param subscriber Mesh 或其他材质消费者\n * @example\n * material.add(subscriber);\n * @returns 无返回值。\n */\n add(subscriber: MaterialSubscriber): void;\n /**\n * 解绑消费者,不销毁共享 Style、Texture 或 GPU 缓存。\n * @param subscriber 需要解绑的消费者\n * @example\n * material.delete(subscriber);\n * @returns 无返回值。\n */\n delete(subscriber: MaterialSubscriber): void;\n /**\n * 最终停止使用时解绑 Style,不销毁共享贴图或 GPU 缓存。\n * @example\n * material.dispose();\n * @returns 无返回值。\n */\n dispose(): void;\n}\n/** 材质只持有 Shader、固定管线状态及 Style 引用,不重复保存显示参数。 */\nabstract class Material implements MaterialLike {\n /** 全局材质 ID。 */\n public readonly id: number = GETID();\n /** 材质类型。 */\n public abstract readonly type: string;\n private _version: number = 0;\n private _key: string = \"\";\n /** 缓存完整几何管线键,避免每个 Mesh 每帧拼接和散列 Shader 长字符串。 */\n private readonly _pipelineKeys = new Map<string, string>();\n private _style: Style;\n private _disposed: boolean = false;\n private readonly _subscribers = new Set<MaterialSubscriber>();\n private _transparent: boolean = false;\n private _depthTest: boolean = false;\n private _depthWrite: boolean = false;\n private _cullMode: GPUCullMode = \"back\";\n private readonly _geometryTypes = new Set<string>();\n private readonly _shaders: ShadersLike = {};\n /**\n * 创建对象并建立初始状态及依赖关联。\n * @param style 共享样式,默认各分区关闭\n * @example\n * const this = new Material(style);\n * @returns 创建的 Material 对象。\n */\n protected constructor(style: Style = new Style()) {\n this._style = style;\n style.add(this);\n }\n /**\n * 当前共享样式。\n * @example\n * const value = material.style;\n * @returns 当前共享样式。\n */\n public get style(): Style {\n return this._style;\n }\n /**\n * 当前共享样式。\n * @param value 新样式;解绑旧引用,并更新材质版本及 Pipeline key\n * @example\n * material.style = value;\n * @returns 无返回值。\n */\n public set style(value: Style) {\n // 检查生命周期状态,防止已释放对象重新建立订阅或资源。\n if (this._disposed) {\n throw new Error(\"Disposed material cannot be reused.\");\n }\n // 仅在属性实际改变时更新状态,避免重复订阅和版本递增。\n if (this._style !== value) {\n this._style.delete(this);\n this._style = value;\n value.add(this);\n this.updateVersion();\n } else {\n return;\n }\n }\n /**\n * 接收共享样式的字段变化,通知当前对象的使用者。\n * @param change 样式变化;区分数值、贴图引用和贴图内容,避免无关缓冲重复上传\n * @example\n * material.onStyleChange(change);\n * @returns 无返回值。\n */\n public onStyleChange(change: StyleChange): void {\n // 检查生命周期状态,防止已释放对象重新建立订阅或资源。\n if (!this._disposed && change.source === this._style) {\n let kind: MaterialChangeKind;\n // 根据变化字段选择更新范围,避免无关属性触发资源重建。\n if (change.field === \"texture\") {\n kind = \"texture\";\n } else {\n // 根据变化字段选择更新范围,避免无关属性触发资源重建。\n if (change.field === \"textureData\") {\n kind = \"textureData\";\n } else {\n // 根据变化字段选择更新范围,避免无关属性触发资源重建。\n if (change.field === \"addressModeU\" || change.field === \"addressModeV\") {\n kind = \"sampler\";\n } else {\n kind = \"style\";\n }\n }\n }\n this.updateVersion(kind);\n } else {\n return;\n }\n }\n /**\n * 最终停止使用时解绑 Style,不销毁共享贴图或 GPU 缓存。\n * @example\n * material.dispose();\n * @returns 无返回值。\n */\n public dispose(): void {\n this._style.delete(this);\n this._subscribers.clear();\n this._disposed = true;\n }\n /**\n * 关联使用当前材质的消费者,Set 自动去重。\n * @param subscriber Mesh 或其他材质消费者\n * @example\n * material.add(subscriber);\n * @returns 无返回值。\n */\n public add(subscriber: MaterialSubscriber): void {\n // 检查生命周期状态,防止已释放对象重新建立订阅或资源。\n if (this._disposed) {\n throw new Error(\"Disposed material cannot be reused.\");\n }\n this._subscribers.add(subscriber);\n }\n /**\n * 解绑消费者,不销毁共享 Style、Texture 或 GPU 缓存。\n * @param subscriber 需要解绑的消费者\n * @example\n * material.delete(subscriber);\n * @returns 无返回值。\n */\n public delete(subscriber: MaterialSubscriber): void {\n this._subscribers.delete(subscriber);\n }\n /**\n * 单一材质版本。\n * @example\n * const value = material.version;\n * @returns 单一材质版本。\n */\n public get version(): number {\n return this._version;\n }\n /**\n * 固定管线状态与 Shader 对应的缓存键。\n * @example\n * const value = material.key;\n * @returns 固定管线状态与 Shader 对应的缓存键。\n */\n public get key(): string {\n return this._key;\n }\n /**\n * 递增单一版本并通知消费者,颜色变化不会改变 key。\n * @param kind 影响范围,手动调用默认全部失效\n * @example\n * material.updateVersion(kind);\n * @returns 无返回值。\n */\n public updateVersion(kind: MaterialChangeKind = \"all\"): void {\n const previousKey: string = this._key;\n this._version++;\n this.updateKey();\n const change: MaterialChange = Object.freeze({\n source: this,\n style: kind === \"all\" || kind === \"style\",\n resources: kind === \"all\" || kind === \"texture\" || previousKey !== this._key,\n });\n // 按订阅快照逐项通知或解绑,避免回调修改集合干扰当前遍历。\n for (const subscriber of [...this._subscribers]) {\n subscriber.onMaterialChange(change);\n }\n }\n /**\n * 所有分区共用的透明混合状态。\n * @example\n * const value = material.transparent;\n * @returns 所有分区共用的透明混合状态。\n */\n public get transparent(): boolean {\n return this._transparent;\n }\n /**\n * 所有分区共用的透明混合状态。\n * @param value 是否开启透明混合\n * @example\n * material.transparent = value;\n * @returns 无返回值。\n */\n public set transparent(value: boolean) {\n // 区分深度和透明状态,保持当前阶段的遮挡与混合约定。\n if (this._transparent !== value) {\n this._transparent = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 是否比较深度,默认 false;关闭时作为最后绘制的覆盖层。\n * @example\n * const value = material.depthTest;\n * @returns 是否比较深度,默认 false;关闭时作为最后绘制的覆盖层。\n */\n public get depthTest(): boolean {\n return this._depthTest;\n }\n /**\n * 是否比较深度,默认 false;关闭时作为最后绘制的覆盖层。\n * @param value 是否执行深度比较,不自动改变 depthWrite / transparent\n * @example\n * material.depthTest = value;\n * @returns 无返回值。\n */\n public set depthTest(value: boolean) {\n // 仅在属性实际改变时更新状态,避免重复订阅和版本递增。\n if (this._depthTest !== value) {\n this._depthTest = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 是否记录通过测试的片元深度,默认 false;普通透明物体通常应关闭。\n * @example\n * const value = material.depthWrite;\n * @returns 是否记录通过测试的片元深度,默认 false;普通透明物体通常应关闭。\n */\n public get depthWrite(): boolean {\n return this._depthWrite;\n }\n /**\n * 是否记录通过测试的片元深度,默认 false;普通透明物体通常应关闭。\n * @param value 是否写入深度,不自动改变 depthTest / transparent\n * @example\n * material.depthWrite = value;\n * @returns 无返回值。\n */\n public set depthWrite(value: boolean) {\n // 仅在属性实际改变时更新状态,避免重复订阅和版本递增。\n if (this._depthWrite !== value) {\n this._depthWrite = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 所有三角面共用的剔除状态。\n * @example\n * const value = material.cullMode;\n * @returns 所有三角面共用的剔除状态。\n */\n public get cullMode(): GPUCullMode {\n return this._cullMode;\n }\n /**\n * 所有三角面共用的剔除状态。\n * @param value 要剔除的面\n * @example\n * material.cullMode = value;\n * @returns 无返回值。\n */\n public set cullMode(value: GPUCullMode) {\n // 仅在属性实际改变时更新状态,避免重复订阅和版本递增。\n if (this._cullMode !== value) {\n this._cullMode = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 追加已使用的几何类型标记;Rect2D 对应 rect,重复绑定不递增版本。\n * @param type Geometry.type\n * @example\n * material.addGeometryType(type);\n * @returns 无返回值\n */\n public addGeometryType(type: string): void {\n let tag: string;\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (type === \"Rect2D\") {\n tag = \"rect\";\n } else {\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (type === \"Poly2D\") {\n tag = \"poly\";\n } else {\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (type === \"NGon2D\") {\n tag = \"ngon\";\n } else {\n tag = type;\n }\n }\n }\n // 检查集合中的关联关系,避免重复处理或遗漏引用。\n if (!this._geometryTypes.has(tag)) {\n this._geometryTypes.add(tag);\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 获取指定几何类型的管线缓存键。\n * 材质 key 描述共享状态,类型后缀区分同一材质的不同几何模板。\n * @param type Geometry.type\n * @example\n * material.getPipelineKey(type);\n * @returns 当前几何对应的管线键\n */\n public getPipelineKey(type: string): string {\n let key: string | undefined = this._pipelineKeys.get(type);\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (key === undefined) {\n let tag: string = type;\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (type === \"Rect2D\") {\n tag = \"rect\";\n } else if (type === \"Poly2D\") {\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n tag = \"poly\";\n } else if (type === \"NGon2D\") {\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n tag = \"ngon\";\n }\n key = this.key + \"_\" + tag;\n this._pipelineKeys.set(type, key);\n }\n return key;\n }\n /**\n * 设置矩形合并三角面顶点着色器源码。\n * @param vertexShader 顶点着色器源码\n * @example\n * material.rectVertexShader = vertexShader;\n * @returns 无返回值。\n */\n public set rectVertexShader(vertexShader: string | undefined) {\n // 着色器源码实际改变后更新管线键,让后续渲染选择新的模板。\n if (this._shaders.rectVertexShader !== vertexShader) {\n this._shaders.rectVertexShader = vertexShader;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 获取矩形合并三角面顶点着色器源码。\n * @example\n * const value = material.rectVertexShader;\n * @returns 顶点着色器源码\n */\n public get rectVertexShader(): string | undefined {\n return this._shaders.rectVertexShader;\n }\n /**\n * 设置矩形合并三角面片元着色器源码。\n * @param fragmentShader 片元着色器源码\n * @example\n * material.rectFragmentShader = fragmentShader;\n * @returns 无返回值。\n */\n public set rectFragmentShader(fragmentShader: string | undefined) {\n // 着色器源码实际改变后更新管线键,让后续渲染选择新的模板。\n if (this._shaders.rectFragmentShader !== fragmentShader) {\n this._shaders.rectFragmentShader = fragmentShader;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 获取矩形合并三角面片元着色器源码。\n * @example\n * const value = material.rectFragmentShader;\n * @returns 片元着色器源码\n */\n public get rectFragmentShader(): string | undefined {\n return this._shaders.rectFragmentShader;\n }\n /**\n * 多边形合并三角面顶点 Shader。\n * @example\n * const value = material.polyVertexShader;\n * @returns 多边形合并三角面顶点 Shader。\n */\n public get polyVertexShader(): string | undefined {\n return this._shaders.polyVertexShader;\n }\n /**\n * 多边形合并三角面顶点 Shader。\n * @param value 多边形顶点 Shader 源码,修改后重建管线\n * @example\n * material.polyVertexShader = value;\n * @returns 无返回值。\n */\n public set polyVertexShader(value: string | undefined) {\n // 着色器源码实际改变后更新管线键,让后续渲染选择新的模板。\n if (this._shaders.polyVertexShader !== value) {\n this._shaders.polyVertexShader = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 多边形填充、边框和点型片段 Shader。\n * @example\n * const value = material.polyFragmentShader;\n * @returns 多边形填充、边框和点型片段 Shader。\n */\n public get polyFragmentShader(): string | undefined {\n return this._shaders.polyFragmentShader;\n }\n /**\n * 多边形填充、边框和点型片段 Shader。\n * @param value 多边形片段 Shader 源码,修改后重建管线\n * @example\n * material.polyFragmentShader = value;\n * @returns 无返回值。\n */\n public set polyFragmentShader(value: string | undefined) {\n // 着色器源码实际改变后更新管线键,让后续渲染选择新的模板。\n if (this._shaders.polyFragmentShader !== value) {\n this._shaders.polyFragmentShader = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 正多边形/圆环的合并三角面顶点 WGSL。\n * @example\n * const value = material.ngonVertexShader;\n * @returns 正多边形/圆环的合并三角面顶点 WGSL。\n */\n public get ngonVertexShader(): string | undefined {\n return this._shaders.ngonVertexShader;\n }\n /**\n * 正多边形/圆环的合并三角面顶点 WGSL。\n * @param value NGon 顶点源码,修改后使管线缓存键失效\n * @example\n * material.ngonVertexShader = value;\n * @returns 无返回值。\n */\n public set ngonVertexShader(value: string | undefined) {\n // 着色器源码实际改变后更新管线键,让后续渲染选择新的模板。\n if (this._shaders.ngonVertexShader !== value) {\n this._shaders.ngonVertexShader = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 正多边形/圆环的填充、边框和点型片段 WGSL。\n * @example\n * const value = material.ngonFragmentShader;\n * @returns 正多边形/圆环的填充、边框和点型片段 WGSL。\n */\n public get ngonFragmentShader(): string | undefined {\n return this._shaders.ngonFragmentShader;\n }\n /**\n * 正多边形/圆环的填充、边框和点型片段 WGSL。\n * @param value NGon 片段源码,修改后使管线缓存键失效\n * @example\n * material.ngonFragmentShader = value;\n * @returns 无返回值。\n */\n public set ngonFragmentShader(value: string | undefined) {\n // 着色器源码实际改变后更新管线键,让后续渲染选择新的模板。\n if (this._shaders.ngonFragmentShader !== value) {\n this._shaders.ngonFragmentShader = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 原生线开关决定是否需要附加 line-list 管线,其余分区通过 Uniform 控制。\n * @example\n * material.updateKey();\n * @returns 无返回值。\n */\n public updateKey(): void {\n /**\n * 为已经排序的几何标签增加统一分隔符。\n * @param tag 已经注册的几何类型标签\n * @example\n * FormatGeometryTag(tag);\n * @returns 带下划线前缀的几何标签。\n */\n const FormatGeometryTag = (tag: string): string => \"_\" + tag;\n const key: string =\n JSON.stringify([\n this.type,\n this._transparent,\n this._cullMode,\n this._depthTest,\n this._depthWrite,\n this._style.wireframe.enabled,\n this._shaders.rectVertexShader,\n this._shaders.rectFragmentShader,\n this._shaders.polyVertexShader,\n this._shaders.polyFragmentShader,\n this._shaders.ngonVertexShader,\n this._shaders.ngonFragmentShader,\n ]) + [...this._geometryTypes].sort().map(FormatGeometryTag).join(\"\");\n // 管线状态真正变化时清空派生键缓存,否则复用原键。\n if (key !== this._key) {\n this._key = key;\n this._pipelineKeys.clear();\n }\n }\n}\nexport default Material;\nexport type {\n BorderAlign,\n MaterialBuffersLike,\n MaterialChange,\n MaterialChangeKind,\n MaterialLike,\n MaterialSubscriber,\n PixelAligned,\n ShadersLike,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Material.js","names":[],"sources":["../../src/scripts/Material/Material.ts"],"sourcesContent":["/// <reference types=\"@webgpu/types\" preserve=\"true\" />\nimport { GETID } from \"../ID\";\nimport Style from \"../Style\";\nimport type { StyleChange, StyleSubscriber, BorderAlign, PixelAligned } from \"../Style\";\ninterface MaterialBuffersLike {\n uniform?: number;\n texture?: number;\n sampler?: number;\n}\ninterface ShadersLike {\n ngonVertexShader?: string;\n ngonFragmentShader?: string;\n polyVertexShader?: string;\n polyFragmentShader?: string;\n rectVertexShader?: string;\n rectFragmentShader?: string;\n}\n/** 通知类别不是独立版本;材质仍只有一个 version。 */\ntype MaterialChangeKind = \"all\" | \"style\" | \"pipeline\" | \"texture\" | \"textureData\" | \"sampler\";\n/** 材质变化影响:样式数组、场景资源引用分别处理。 */\ninterface MaterialChange {\n readonly source: MaterialLike;\n readonly style: boolean;\n readonly resources: boolean;\n}\n/** 材质消费者,回调只安排下一帧工作。 */\ninterface MaterialSubscriber {\n /**\n * 接收材质变化通知,区分样式数据与资源引用的更新。\n * @param change 变化来源、区域及字段信息\n * @example\n * materialSubscriber.onMaterialChange(change);\n * @returns 无返回值。\n */\n onMaterialChange(change: MaterialChange): void;\n}\ninterface MaterialLike extends StyleSubscriber {\n readonly id: number;\n readonly type: string;\n readonly key: string;\n readonly version: number;\n style: Style;\n transparent: boolean;\n depthTest: boolean;\n depthWrite: boolean;\n cullMode: GPUCullMode;\n /**\n * 追加已使用的几何类型标记;Rect2D 对应 rect,重复绑定不递增版本。\n * @param type Geometry.type\n * @example\n * material.addGeometryType(type);\n * @returns 无返回值\n */\n addGeometryType(type: string): void;\n /**\n * 获取指定几何类型的管线缓存键。\n * 材质 key 描述共享状态,类型后缀区分同一材质的不同几何模板。\n * @param type Geometry.type\n * @example\n * material.getPipelineKey(type);\n * @returns 当前几何对应的管线键\n */\n getPipelineKey(type: string): string;\n rectVertexShader: string | undefined;\n rectFragmentShader: string | undefined;\n polyVertexShader: string | undefined;\n polyFragmentShader: string | undefined;\n ngonVertexShader: string | undefined;\n ngonFragmentShader: string | undefined;\n /**\n * 原生线开关决定是否需要附加 line-list 管线,其余分区通过 Uniform 控制。\n * @example\n * material.updateKey();\n * @returns 无返回值。\n */\n updateKey(): void;\n /**\n * 递增单一版本并通知消费者,颜色变化不会改变 key。\n * @param kind 影响范围,手动调用默认全部失效\n * @example\n * material.updateVersion(kind);\n * @returns 无返回值。\n */\n updateVersion(kind?: MaterialChangeKind): void;\n /**\n * 关联使用当前材质的消费者,Set 自动去重。\n * @param subscriber Mesh 或其他材质消费者\n * @example\n * material.add(subscriber);\n * @returns 无返回值。\n */\n add(subscriber: MaterialSubscriber): void;\n /**\n * 解绑消费者,不销毁共享 Style、Texture 或 GPU 缓存。\n * @param subscriber 需要解绑的消费者\n * @example\n * material.delete(subscriber);\n * @returns 无返回值。\n */\n delete(subscriber: MaterialSubscriber): void;\n /**\n * 最终停止使用时解绑 Style,不销毁共享贴图或 GPU 缓存。\n * @example\n * material.dispose();\n * @returns 无返回值。\n */\n dispose(): void;\n}\n/** 材质只持有 Shader、固定管线状态及 Style 引用,不重复保存显示参数。 */\nabstract class Material implements MaterialLike {\n /** 全局材质 ID。 */\n public readonly id: number = GETID();\n /** 材质类型。 */\n public abstract readonly type: string;\n private _version: number = 0;\n private _key: string = \"\";\n /** 缓存完整几何管线键,避免每个 Mesh 每帧拼接和散列 Shader 长字符串。 */\n private readonly _pipelineKeys = new Map<string, string>();\n private _style: Style;\n private _disposed: boolean = false;\n private readonly _subscribers = new Set<MaterialSubscriber>();\n private _transparent: boolean = false;\n private _depthTest: boolean = false;\n private _depthWrite: boolean = false;\n private _cullMode: GPUCullMode = \"back\";\n private readonly _geometryTypes = new Set<string>();\n private readonly _shaders: ShadersLike = {};\n /**\n * 创建对象并建立初始状态及依赖关联。\n * @param style 共享样式,默认各分区关闭\n * @example\n * const this = new Material(style);\n * @returns 创建的 Material 对象。\n */\n protected constructor(style: Style = new Style()) {\n this._style = style;\n style.add(this);\n }\n /**\n * 当前共享样式。\n * @example\n * const value = material.style;\n * @returns 当前共享样式。\n */\n public get style(): Style {\n return this._style;\n }\n /**\n * 当前共享样式。\n * @param value 新样式;解绑旧引用,并更新材质版本及 Pipeline key\n * @example\n * material.style = value;\n * @returns 无返回值。\n */\n public set style(value: Style) {\n // 检查生命周期状态,防止已释放对象重新建立订阅或资源。\n if (this._disposed) {\n throw new Error(\"Disposed material cannot be reused.\");\n }\n // 仅在属性实际改变时更新状态,避免重复订阅和版本递增。\n if (this._style !== value) {\n this._style.delete(this);\n this._style = value;\n value.add(this);\n this.updateVersion();\n } else {\n return;\n }\n }\n /**\n * 接收共享样式的字段变化,通知当前对象的使用者。\n * @param change 样式变化;区分数值、贴图引用和贴图内容,避免无关缓冲重复上传\n * @example\n * material.onStyleChange(change);\n * @returns 无返回值。\n */\n public onStyleChange(change: StyleChange): void {\n // 检查生命周期状态,防止已释放对象重新建立订阅或资源。\n if (!this._disposed && change.source === this._style) {\n let kind: MaterialChangeKind;\n // 根据变化字段选择更新范围,避免无关属性触发资源重建。\n if (change.field === \"texture\") {\n kind = \"texture\";\n } else {\n // 根据变化字段选择更新范围,避免无关属性触发资源重建。\n if (change.field === \"textureData\") {\n kind = \"textureData\";\n } else {\n // 根据变化字段选择更新范围,避免无关属性触发资源重建。\n if (change.field === \"addressModeU\" || change.field === \"addressModeV\") {\n kind = \"sampler\";\n } else {\n kind = \"style\";\n }\n }\n }\n this.updateVersion(kind);\n } else {\n return;\n }\n }\n /**\n * 最终停止使用时解绑 Style,不销毁共享贴图或 GPU 缓存。\n * @example\n * material.dispose();\n * @returns 无返回值。\n */\n public dispose(): void {\n this._style.delete(this);\n this._subscribers.clear();\n this._disposed = true;\n }\n /**\n * 关联使用当前材质的消费者,Set 自动去重。\n * @param subscriber Mesh 或其他材质消费者\n * @example\n * material.add(subscriber);\n * @returns 无返回值。\n */\n public add(subscriber: MaterialSubscriber): void {\n // 检查生命周期状态,防止已释放对象重新建立订阅或资源。\n if (this._disposed) {\n throw new Error(\"Disposed material cannot be reused.\");\n }\n this._subscribers.add(subscriber);\n }\n /**\n * 解绑消费者,不销毁共享 Style、Texture 或 GPU 缓存。\n * @param subscriber 需要解绑的消费者\n * @example\n * material.delete(subscriber);\n * @returns 无返回值。\n */\n public delete(subscriber: MaterialSubscriber): void {\n this._subscribers.delete(subscriber);\n }\n /**\n * 单一材质版本。\n * @example\n * const value = material.version;\n * @returns 单一材质版本。\n */\n public get version(): number {\n return this._version;\n }\n /**\n * 固定管线状态与 Shader 对应的缓存键。\n * @example\n * const value = material.key;\n * @returns 固定管线状态与 Shader 对应的缓存键。\n */\n public get key(): string {\n return this._key;\n }\n /**\n * 递增单一版本并通知消费者,颜色变化不会改变 key。\n * @param kind 影响范围,手动调用默认全部失效\n * @example\n * material.updateVersion(kind);\n * @returns 无返回值。\n */\n public updateVersion(kind: MaterialChangeKind = \"all\"): void {\n const previousKey: string = this._key;\n this._version++;\n this.updateKey();\n const change: MaterialChange = Object.freeze({\n source: this,\n style: kind === \"all\" || kind === \"style\",\n resources: kind === \"all\" || kind === \"texture\" || previousKey !== this._key,\n });\n // 按订阅快照逐项通知或解绑,避免回调修改集合干扰当前遍历。\n for (const subscriber of [...this._subscribers]) {\n subscriber.onMaterialChange(change);\n }\n }\n /**\n * 所有分区共用的透明混合状态。\n * @example\n * const value = material.transparent;\n * @returns 所有分区共用的透明混合状态。\n */\n public get transparent(): boolean {\n return this._transparent;\n }\n /**\n * 所有分区共用的透明混合状态。\n * @param value 是否开启透明混合\n * @example\n * material.transparent = value;\n * @returns 无返回值。\n */\n public set transparent(value: boolean) {\n // 区分深度和透明状态,保持当前阶段的遮挡与混合约定。\n if (this._transparent !== value) {\n this._transparent = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 是否比较深度,默认 false;关闭时作为最后绘制的覆盖层。\n * @example\n * const value = material.depthTest;\n * @returns 是否比较深度,默认 false;关闭时作为最后绘制的覆盖层。\n */\n public get depthTest(): boolean {\n return this._depthTest;\n }\n /**\n * 是否比较深度,默认 false;关闭时作为最后绘制的覆盖层。\n * @param value 是否执行深度比较,不自动改变 depthWrite / transparent\n * @example\n * material.depthTest = value;\n * @returns 无返回值。\n */\n public set depthTest(value: boolean) {\n // 仅在属性实际改变时更新状态,避免重复订阅和版本递增。\n if (this._depthTest !== value) {\n this._depthTest = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 是否记录通过测试的片元深度,默认 false;普通透明物体通常应关闭。\n * @example\n * const value = material.depthWrite;\n * @returns 是否记录通过测试的片元深度,默认 false;普通透明物体通常应关闭。\n */\n public get depthWrite(): boolean {\n return this._depthWrite;\n }\n /**\n * 是否记录通过测试的片元深度,默认 false;普通透明物体通常应关闭。\n * @param value 是否写入深度,不自动改变 depthTest / transparent\n * @example\n * material.depthWrite = value;\n * @returns 无返回值。\n */\n public set depthWrite(value: boolean) {\n // 仅在属性实际改变时更新状态,避免重复订阅和版本递增。\n if (this._depthWrite !== value) {\n this._depthWrite = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 所有三角面共用的剔除状态。\n * @example\n * const value = material.cullMode;\n * @returns 所有三角面共用的剔除状态。\n */\n public get cullMode(): GPUCullMode {\n return this._cullMode;\n }\n /**\n * 所有三角面共用的剔除状态。\n * @param value 要剔除的面\n * @example\n * material.cullMode = value;\n * @returns 无返回值。\n */\n public set cullMode(value: GPUCullMode) {\n // 仅在属性实际改变时更新状态,避免重复订阅和版本递增。\n if (this._cullMode !== value) {\n this._cullMode = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 追加已使用的几何类型标记;Rect2D 对应 rect,重复绑定不递增版本。\n * @param type Geometry.type\n * @example\n * material.addGeometryType(type);\n * @returns 无返回值\n */\n public addGeometryType(type: string): void {\n let tag: string;\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (type === \"Rect2D\") {\n tag = \"rect\";\n } else {\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (type === \"Poly2D\" || type === \"Text\") {\n tag = \"poly\";\n } else {\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (type === \"NGon2D\") {\n tag = \"ngon\";\n } else {\n tag = type;\n }\n }\n }\n // 检查集合中的关联关系,避免重复处理或遗漏引用。\n if (!this._geometryTypes.has(tag)) {\n this._geometryTypes.add(tag);\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 获取指定几何类型的管线缓存键。\n * 材质 key 描述共享状态,类型后缀区分同一材质的不同几何模板。\n * @param type Geometry.type\n * @example\n * material.getPipelineKey(type);\n * @returns 当前几何对应的管线键\n */\n public getPipelineKey(type: string): string {\n let key: string | undefined = this._pipelineKeys.get(type);\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (key === undefined) {\n let tag: string = type;\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (type === \"Rect2D\") {\n tag = \"rect\";\n } else if (type === \"Poly2D\" || type === \"Text\") {\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n tag = \"poly\";\n } else if (type === \"NGon2D\") {\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n tag = \"ngon\";\n }\n key = this.key + \"_\" + tag;\n this._pipelineKeys.set(type, key);\n }\n return key;\n }\n /**\n * 设置矩形合并三角面顶点着色器源码。\n * @param vertexShader 顶点着色器源码\n * @example\n * material.rectVertexShader = vertexShader;\n * @returns 无返回值。\n */\n public set rectVertexShader(vertexShader: string | undefined) {\n // 着色器源码实际改变后更新管线键,让后续渲染选择新的模板。\n if (this._shaders.rectVertexShader !== vertexShader) {\n this._shaders.rectVertexShader = vertexShader;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 获取矩形合并三角面顶点着色器源码。\n * @example\n * const value = material.rectVertexShader;\n * @returns 顶点着色器源码\n */\n public get rectVertexShader(): string | undefined {\n return this._shaders.rectVertexShader;\n }\n /**\n * 设置矩形合并三角面片元着色器源码。\n * @param fragmentShader 片元着色器源码\n * @example\n * material.rectFragmentShader = fragmentShader;\n * @returns 无返回值。\n */\n public set rectFragmentShader(fragmentShader: string | undefined) {\n // 着色器源码实际改变后更新管线键,让后续渲染选择新的模板。\n if (this._shaders.rectFragmentShader !== fragmentShader) {\n this._shaders.rectFragmentShader = fragmentShader;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 获取矩形合并三角面片元着色器源码。\n * @example\n * const value = material.rectFragmentShader;\n * @returns 片元着色器源码\n */\n public get rectFragmentShader(): string | undefined {\n return this._shaders.rectFragmentShader;\n }\n /**\n * 多边形合并三角面顶点 Shader。\n * @example\n * const value = material.polyVertexShader;\n * @returns 多边形合并三角面顶点 Shader。\n */\n public get polyVertexShader(): string | undefined {\n return this._shaders.polyVertexShader;\n }\n /**\n * 多边形合并三角面顶点 Shader。\n * @param value 多边形顶点 Shader 源码,修改后重建管线\n * @example\n * material.polyVertexShader = value;\n * @returns 无返回值。\n */\n public set polyVertexShader(value: string | undefined) {\n // 着色器源码实际改变后更新管线键,让后续渲染选择新的模板。\n if (this._shaders.polyVertexShader !== value) {\n this._shaders.polyVertexShader = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 多边形填充、边框和点型片段 Shader。\n * @example\n * const value = material.polyFragmentShader;\n * @returns 多边形填充、边框和点型片段 Shader。\n */\n public get polyFragmentShader(): string | undefined {\n return this._shaders.polyFragmentShader;\n }\n /**\n * 多边形填充、边框和点型片段 Shader。\n * @param value 多边形片段 Shader 源码,修改后重建管线\n * @example\n * material.polyFragmentShader = value;\n * @returns 无返回值。\n */\n public set polyFragmentShader(value: string | undefined) {\n // 着色器源码实际改变后更新管线键,让后续渲染选择新的模板。\n if (this._shaders.polyFragmentShader !== value) {\n this._shaders.polyFragmentShader = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 正多边形/圆环的合并三角面顶点 WGSL。\n * @example\n * const value = material.ngonVertexShader;\n * @returns 正多边形/圆环的合并三角面顶点 WGSL。\n */\n public get ngonVertexShader(): string | undefined {\n return this._shaders.ngonVertexShader;\n }\n /**\n * 正多边形/圆环的合并三角面顶点 WGSL。\n * @param value NGon 顶点源码,修改后使管线缓存键失效\n * @example\n * material.ngonVertexShader = value;\n * @returns 无返回值。\n */\n public set ngonVertexShader(value: string | undefined) {\n // 着色器源码实际改变后更新管线键,让后续渲染选择新的模板。\n if (this._shaders.ngonVertexShader !== value) {\n this._shaders.ngonVertexShader = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 正多边形/圆环的填充、边框和点型片段 WGSL。\n * @example\n * const value = material.ngonFragmentShader;\n * @returns 正多边形/圆环的填充、边框和点型片段 WGSL。\n */\n public get ngonFragmentShader(): string | undefined {\n return this._shaders.ngonFragmentShader;\n }\n /**\n * 正多边形/圆环的填充、边框和点型片段 WGSL。\n * @param value NGon 片段源码,修改后使管线缓存键失效\n * @example\n * material.ngonFragmentShader = value;\n * @returns 无返回值。\n */\n public set ngonFragmentShader(value: string | undefined) {\n // 着色器源码实际改变后更新管线键,让后续渲染选择新的模板。\n if (this._shaders.ngonFragmentShader !== value) {\n this._shaders.ngonFragmentShader = value;\n this.updateVersion(\"pipeline\");\n } else {\n return;\n }\n }\n /**\n * 原生线开关决定是否需要附加 line-list 管线,其余分区通过 Uniform 控制。\n * @example\n * material.updateKey();\n * @returns 无返回值。\n */\n public updateKey(): void {\n /**\n * 为已经排序的几何标签增加统一分隔符。\n * @param tag 已经注册的几何类型标签\n * @example\n * FormatGeometryTag(tag);\n * @returns 带下划线前缀的几何标签。\n */\n const FormatGeometryTag = (tag: string): string => \"_\" + tag;\n const key: string =\n JSON.stringify([\n this.type,\n this._transparent,\n this._cullMode,\n this._depthTest,\n this._depthWrite,\n this._style.wireframe.enabled,\n this._shaders.rectVertexShader,\n this._shaders.rectFragmentShader,\n this._shaders.polyVertexShader,\n this._shaders.polyFragmentShader,\n this._shaders.ngonVertexShader,\n this._shaders.ngonFragmentShader,\n ]) + [...this._geometryTypes].sort().map(FormatGeometryTag).join(\"\");\n // 管线状态真正变化时清空派生键缓存,否则复用原键。\n if (key !== this._key) {\n this._key = key;\n this._pipelineKeys.clear();\n }\n }\n}\nexport default Material;\nexport type {\n BorderAlign,\n MaterialBuffersLike,\n MaterialChange,\n MaterialChangeKind,\n MaterialLike,\n MaterialSubscriber,\n PixelAligned,\n ShadersLike,\n};\n"],"mappings":"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"}
|
|
@@ -100,10 +100,15 @@ var DevelopmentValidator = class {
|
|
|
100
100
|
if (geometry.style.solid.enabled && ![
|
|
101
101
|
"Rect2D",
|
|
102
102
|
"Poly2D",
|
|
103
|
-
"NGon2D"
|
|
103
|
+
"NGon2D",
|
|
104
|
+
"Text"
|
|
104
105
|
].includes(geometry.type)) this.error("UNSUPPORTED_SOLID_TYPE", mesh.id, "未提供该类型的实体面 Shader。", mesh);
|
|
105
106
|
if (!(mesh instanceof IMesh)) {
|
|
106
|
-
if ([
|
|
107
|
+
if ([
|
|
108
|
+
"Poly2D",
|
|
109
|
+
"NGon2D",
|
|
110
|
+
"Text"
|
|
111
|
+
].includes(geometry.type) && material.style.solid.borderWidth > 0) this.warn("UNSUPPORTED_SDF_BORDER", mesh.id, "Poly2D/NGon2D/Text 不使用矩形 SDF 边框,请启用 edge 分区。", mesh);
|
|
107
112
|
if (geometry.type === "Poly2D" && (geometry.style.join.type !== material.style.join.type || geometry.style.join.seg !== material.style.join.seg)) this.warn("POLY_JOIN_STYLE", mesh.id, "连接拓扑由 Geometry.style.join 生成,建议几何与材质共享 Style。", mesh);
|
|
108
113
|
for (const area of [
|
|
109
114
|
"solid",
|
|
@@ -115,7 +120,11 @@ var DevelopmentValidator = class {
|
|
|
115
120
|
} else {
|
|
116
121
|
if (geometry.style.wireframe.enabled && mesh.count > 1) this.warn("IMESH_NATIVE_LINES", mesh.id, "原生 line-list 使用逐实例顺序回退;只用三角面时才合并为一次绘制。", mesh);
|
|
117
122
|
for (const raw of mesh.raws.map.values()) {
|
|
118
|
-
if ([
|
|
123
|
+
if ([
|
|
124
|
+
"Poly2D",
|
|
125
|
+
"NGon2D",
|
|
126
|
+
"Text"
|
|
127
|
+
].includes(geometry.type) && raw.style.solid.borderWidth > 0) this.warn("UNSUPPORTED_SDF_BORDER", mesh.id, "Poly2D/NGon2D/Text 不使用矩形 SDF 边框,请启用 edge 分区。", mesh);
|
|
119
128
|
if (geometry.type === "Poly2D" && (raw.style.join.type !== geometry.style.join.type || raw.style.join.seg !== geometry.style.join.seg)) this.warn("IMESH_JOIN_TEMPLATE", mesh.id, "连接类型和精度由 Geometry.style.join 决定,Raw 不生成独立拓扑。", mesh);
|
|
120
129
|
for (const area of [
|
|
121
130
|
"solid",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DevelopmentValidator.js","names":[],"sources":["../../src/scripts/Render/DevelopmentValidator.ts"],"sourcesContent":["import IMesh from \"../IMesh\";\nimport type Camera from \"../Camera\";\nimport type { GeoData, GeoPartDataLike } from \"../Geometry\";\nimport type { Material2d } from \"../global-types\";\nimport type { MeshLike } from \"../Mesh\";\ninterface DevelopmentValidatorLike {\n /**\n * 检查 Camera 是否能生成有效矩阵与 Uniform。\n * @param camera 当前 Camera\n * @example\n * developmentValidator.validateCamera(camera);\n * @returns 无返回值\n */\n validateCamera(camera: Camera): void;\n /**\n * 检查材质样式数据和分区配置。\n * @param material 当前材质\n * @example\n * developmentValidator.validateMaterial(material);\n * @returns 无返回值\n */\n validateMaterial(material: Material2d): void;\n /**\n * 检查 Geometry 各缓冲分区的长度、有限值和索引范围。\n * @param geometry 当前 Geometry\n * @example\n * developmentValidator.validateGeometry(geometry);\n * @returns 无返回值\n */\n validateGeometry(geometry: GeoData): void;\n /**\n * 检查 Mesh 的 Geometry 与 Material 是否能组成目标边框绘制模式。\n * @param mesh 当前 Mesh\n * @example\n * developmentValidator.validateMesh(mesh);\n * @returns 无返回值\n */\n validateMesh(mesh: MeshLike): void;\n}\n/**\n * 开发模式 GPUBuffer 数据与渲染配置校验器。\n * 只报告 TypeScript 无法约束的运行时数据问题,并按对象和问题类型去重。\n * @class\n */\nclass DevelopmentValidator implements DevelopmentValidatorLike {\n private readonly reported: Set<string> = new Set<string>();\n /**\n * 检查 Camera 是否能生成有效矩阵与 Uniform。\n * @param camera 当前 Camera\n * @example\n * developmentValidator.validateCamera(camera);\n * @returns 无返回值\n */\n public validateCamera(camera: Camera): void {\n // 校验数值有效性,防止非有限值进入几何或 GPU 数据。\n if (!Number.isFinite(camera.width) || camera.width <= 0) {\n this.error(\"CAMERA_WIDTH\", camera.id, \"Camera width 必须是大于 0 的有限数。\", camera);\n }\n // 校验数值有效性,防止非有限值进入几何或 GPU 数据。\n if (!Number.isFinite(camera.height) || camera.height <= 0) {\n this.error(\"CAMERA_HEIGHT\", camera.id, \"Camera height 必须是大于 0 的有限数。\", camera);\n }\n // 校验数值有效性,防止非有限值进入几何或 GPU 数据。\n if (!Number.isFinite(camera.zoom) || camera.zoom <= 0) {\n this.error(\"CAMERA_ZOOM\", camera.id, \"Camera zoom 必须是大于 0 的有限数。\", camera);\n }\n }\n /**\n * 检查材质样式数据和分区配置。\n * @param material 当前材质\n * @example\n * developmentValidator.validateMaterial(material);\n * @returns 无返回值\n */\n public validateMaterial(material: Material2d): void {\n // 区分深度和透明状态,保持当前阶段的遮挡与混合约定。\n if (material.transparent && material.depthWrite) {\n this.warn(\n \"TRANSPARENT_DEPTH_WRITE\",\n material.id,\n \"透明混合同时写入深度可能挡住后方透明片元;通常使用 depthTest=true、depthWrite=false。\",\n material,\n );\n }\n // 区分深度和透明状态,保持当前阶段的遮挡与混合约定。\n if (!material.depthTest && material.depthWrite) {\n this.warn(\n \"DEPTH_WRITE_WITHOUT_TEST\",\n material.id,\n \"已关闭深度测试但仍写深度;它不会检查场景遮挡,通常 UI 两项都关闭。\",\n material,\n );\n }\n const { solid, wireframe, edge, points } = material.style;\n // 按轮廓顺序处理节点,使顶点属性与生成索引保持对应。\n for (const part of [solid, wireframe, edge, points]) {\n // 校验数值有效性,防止非有限值进入几何或 GPU 数据。\n if (!Number.isFinite(part.opacity) || part.opacity < 0 || part.opacity > 1) {\n this.error(\n \"STYLE_OPACITY_\" + part.area,\n material.id,\n \"分区透明度必须位于 0 到 1。\",\n material,\n );\n }\n }\n // 按轮廓顺序处理节点,使顶点属性与生成索引保持对应。\n for (const part of [solid, edge, points]) {\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (part.texture !== undefined && !part.enabled) {\n this.warn(\n \"UNUSED_TEXTURE_\" + part.area,\n material.id,\n \"分区已设置贴图,但 enabled 尚未开启。\",\n material,\n );\n }\n }\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (points.enabled && !points.vertices && !points.midpoints) {\n this.warn(\n \"EMPTY_POINT_SELECTION\",\n material.id,\n \"关键点已开启,但未选择顶点或边中点;不会生成点型。\",\n material,\n );\n }\n }\n /**\n * 检查 Geometry 各缓冲分区的长度、有限值和索引范围。\n * @param geometry 当前 Geometry\n * @example\n * developmentValidator.validateGeometry(geometry);\n * @returns 无返回值\n */\n public validateGeometry(geometry: GeoData): void {\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (geometry.geometry !== undefined) {\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (\n geometry.normal === undefined ||\n geometry.uv === undefined ||\n geometry.index === undefined ||\n geometry.vertexType === undefined ||\n geometry.position === undefined ||\n geometry.miterScale === undefined\n ) {\n this.error(\n \"GEOMETRY_MISSING\",\n geometry.id,\n \"合并三角面缺少 normal、uv、index、vertexType、position 或 miterScale。\",\n geometry,\n );\n } else {\n const vertexCount: number = geometry.geometry.length / 2;\n this.validatePart(\n geometry.id,\n \"geometry\",\n {\n geometry: geometry.geometry,\n normal: geometry.normal,\n uv: geometry.uv,\n index: geometry.index,\n miterScale: geometry.miterScale,\n },\n 3,\n true,\n geometry,\n );\n /**\n * 检查合并几何中的顶点用途是否属于支持的三种类型。\n * @param value 本次输入值\n * @example\n * HasInvalidVertexType(value);\n * @returns 当前类型值是否不受支持。\n */\n const HasInvalidVertexType = (value: number): boolean =>\n value !== 0 && value !== 0.5 && value !== 1;\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (\n geometry.vertexType.length !== vertexCount ||\n geometry.vertexType.some(HasInvalidVertexType)\n ) {\n this.error(\n \"VERTEX_TYPE\",\n geometry.id,\n \"vertexType 必须逐顶点对应,且只能为 0、0.5、1。\",\n geometry,\n );\n }\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (\n geometry.position.length !== geometry.geometry.length ||\n GetInvalidNumberIndex(geometry.position) !== -1\n ) {\n this.error(\n \"GEOMETRY_POSITION\",\n geometry.id,\n \"position 必须与 geometry 等长且全部为有限数。\",\n geometry,\n );\n }\n // 按顶点顺序写入坐标及配套属性,保持各数组长度一致。\n for (let offset: number = 0; offset < geometry.index.length; offset += 3) {\n const type: number = geometry.vertexType[geometry.index[offset]];\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (\n geometry.vertexType[geometry.index[offset + 1]] !== type ||\n geometry.vertexType[geometry.index[offset + 2]] !== type\n ) {\n this.error(\n \"TRIANGLE_VERTEX_TYPE\",\n geometry.id,\n \"一个三角面的三个顶点必须属于同一 vertexType。\",\n geometry,\n );\n break;\n }\n }\n }\n }\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (geometry.linePoints !== undefined) {\n this.validatePart(geometry.id, \"linePoints\", geometry.linePoints, 2, false, geometry);\n }\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (geometry.style.wireframe.enabled && geometry.linePoints === undefined) {\n this.error(\n \"WIREFRAME_DATA\",\n geometry.id,\n \"Geometry wireframe 已开启,但没有生成 linePoints。\",\n geometry,\n );\n }\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (\n geometry.style.edge.uvRepeat !== 1 &&\n !geometry.style.wireframe.enabled &&\n !geometry.style.edge.enabled\n ) {\n this.warn(\"UNUSED_UV_REPEAT\", geometry.id, \"uvRepeat 只作用于边框,当前值不会被使用。\", geometry);\n }\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (geometry.uniformData.length !== 4 || GetInvalidNumberIndex(geometry.uniformData) !== -1) {\n this.error(\n \"GEOMETRY_UNIFORM\",\n geometry.id,\n \"Geometry uniformData 必须包含 4 个有限数。\",\n geometry,\n );\n }\n }\n /**\n * 检查 Mesh 的 Geometry 与 Material 是否能组成目标边框绘制模式。\n * @param mesh 当前 Mesh\n * @example\n * developmentValidator.validateMesh(mesh);\n * @returns 无返回值\n */\n public validateMesh(mesh: MeshLike): void {\n const geometry = mesh.data;\n const material = mesh.material;\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (geometry !== undefined && material !== undefined) {\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (geometry.style.solid.enabled && ![\"Rect2D\", \"Poly2D\", \"NGon2D\"].includes(geometry.type)) {\n this.error(\"UNSUPPORTED_SOLID_TYPE\", mesh.id, \"未提供该类型的实体面 Shader。\", mesh);\n }\n // 区分输入数据形态,使用与实际类型匹配的处理方式。\n if (!(mesh instanceof IMesh)) {\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if ([\"Poly2D\", \"NGon2D\"].includes(geometry.type) && material.style.solid.borderWidth > 0) {\n this.warn(\n \"UNSUPPORTED_SDF_BORDER\",\n mesh.id,\n \"Poly2D/NGon2D 不使用矩形 SDF 边框,请启用 edge 分区。\",\n mesh,\n );\n }\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (\n geometry.type === \"Poly2D\" &&\n (geometry.style.join.type !== material.style.join.type ||\n geometry.style.join.seg !== material.style.join.seg)\n ) {\n this.warn(\n \"POLY_JOIN_STYLE\",\n mesh.id,\n \"连接拓扑由 Geometry.style.join 生成,建议几何与材质共享 Style。\",\n mesh,\n );\n }\n // 按轮廓顺序处理节点,使顶点属性与生成索引保持对应。\n for (const area of [\"solid\", \"wireframe\", \"edge\", \"points\"] as const) {\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (geometry.style[area].enabled !== material.style[area].enabled) {\n this.warn(\n \"STYLE_ENABLED_\" + area,\n mesh.id,\n \"几何与材质的分区开关不同;建议引用同一个 Style。\",\n mesh,\n );\n }\n }\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (\n geometry.style !== material.style &&\n geometry.style.edge.enabled &&\n (geometry.style.edge.width !== material.style.edge.width ||\n geometry.style.edge.borderAlign !== material.style.edge.borderAlign)\n ) {\n this.warn(\n \"STYLE_EDGE_PARAMETERS\",\n mesh.id,\n \"几何与材质的实体边框参数不同;几何宽度参与生成,建议共享 Style。\",\n mesh,\n );\n }\n } else {\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (geometry.style.wireframe.enabled && mesh.count > 1) {\n this.warn(\n \"IMESH_NATIVE_LINES\",\n mesh.id,\n \"原生 line-list 使用逐实例顺序回退;只用三角面时才合并为一次绘制。\",\n mesh,\n );\n }\n // 遍历当前缓存条目,按实际引用关系处理资源。\n for (const raw of mesh.raws.map.values()) {\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if ([\"Poly2D\", \"NGon2D\"].includes(geometry.type) && raw.style.solid.borderWidth > 0) {\n this.warn(\n \"UNSUPPORTED_SDF_BORDER\",\n mesh.id,\n \"Poly2D/NGon2D 不使用矩形 SDF 边框,请启用 edge 分区。\",\n mesh,\n );\n }\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (\n geometry.type === \"Poly2D\" &&\n (raw.style.join.type !== geometry.style.join.type ||\n raw.style.join.seg !== geometry.style.join.seg)\n ) {\n this.warn(\n \"IMESH_JOIN_TEMPLATE\",\n mesh.id,\n \"连接类型和精度由 Geometry.style.join 决定,Raw 不生成独立拓扑。\",\n mesh,\n );\n }\n // 按轮廓顺序处理节点,使顶点属性与生成索引保持对应。\n for (const area of [\"solid\", \"wireframe\", \"edge\", \"points\"] as const) {\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (raw.style[area].enabled && !geometry.style[area].enabled) {\n this.warn(\n \"IMESH_MISSING_\" + area,\n mesh.id,\n \"实例启用了模板未生成的 \" + area + \" 分区;请先配置 Geometry.style。\",\n mesh,\n );\n }\n }\n // 按轮廓顺序处理节点,使顶点属性与生成索引保持对应。\n for (const area of [\"solid\", \"edge\", \"points\"] as const) {\n // 处理转向与退化边界,避免零面积三角面或不稳定法线。\n if (\n raw.style[area].addressModeU !== material.style[area].addressModeU ||\n raw.style[area].addressModeV !== material.style[area].addressModeV\n ) {\n this.warn(\n \"IMESH_SAMPLER_\" + area,\n mesh.id,\n \"一次绘制共用 Material.style 的采样器,Raw.style 的寻址方式不单独生效。\",\n mesh,\n );\n }\n }\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (\n raw.style.edge.enabled &&\n (raw.style.edge.width !== geometry.style.edge.width ||\n raw.style.edge.uvRepeat !== geometry.style.edge.uvRepeat)\n ) {\n this.warn(\n \"IMESH_EDGE_TEMPLATE\",\n mesh.id,\n \"实例实体边框宽度和 UV Repeat 由共用 Geometry.style 生成,Raw 不重建独立几何。\",\n mesh,\n );\n }\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (\n raw.style.points.enabled &&\n (raw.style.points.radius !== geometry.style.points.radius ||\n raw.style.points.segments !== geometry.style.points.segments ||\n raw.style.points.vertices !== geometry.style.points.vertices ||\n raw.style.points.midpoints !== geometry.style.points.midpoints ||\n raw.style.points.minPointsLength !== geometry.style.points.minPointsLength ||\n raw.style.points.minEdgePointsLength !==\n geometry.style.points.minEdgePointsLength)\n ) {\n this.warn(\n \"IMESH_POINTS_TEMPLATE\",\n mesh.id,\n \"关键点半径、段数、选择规则由共用 Geometry.style 决定。\",\n mesh,\n );\n }\n }\n return;\n }\n } else {\n this.error(\"MESH_RESOURCE\", mesh.id, \"Mesh 缺少 Geometry 或 Material。\", mesh);\n return;\n }\n }\n /**\n * 检查单个几何缓冲分区。\n * @param geometryId Geometry ID\n * @param name 缓冲分区名\n * @param data CPU 几何数据\n * @param primitiveSize 每个图元使用的索引数\n * @param requireMiterScale 是否要求 miterScale\n * @param source 日志附带对象\n * @example\n * this.validatePart(geometryId, name, data, primitiveSize, requireMiterScale, source);\n * @returns 无返回值\n */\n private validatePart(\n geometryId: number,\n name: string,\n data: GeoPartDataLike,\n primitiveSize: 2 | 3,\n requireMiterScale: boolean,\n source: object,\n ): void {\n const vertexCount: number = data.geometry.length / 2;\n // 区分空数据和有效内容,空集合不创建可绘制资源。\n if (data.geometry.length % 2 !== 0) {\n this.error(\n \"GEOMETRY_VERTEX_\" + name,\n geometryId,\n name + \" 顶点数组长度必须是 2 的倍数。\",\n source,\n );\n }\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (data.normal.length !== data.geometry.length) {\n this.error(\"GEOMETRY_NORMAL_\" + name, geometryId, name + \" 法线数量必须与顶点数量一致。\", source);\n }\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (data.uv.length !== data.geometry.length) {\n this.error(\"GEOMETRY_UV_\" + name, geometryId, name + \" UV 数量必须与顶点数量一致。\", source);\n }\n // 区分空数据和有效内容,空集合不创建可绘制资源。\n if (data.index.length % primitiveSize !== 0) {\n this.error(\n \"GEOMETRY_INDEX_COUNT_\" + name,\n geometryId,\n name + \" 索引数量与图元类型不匹配。\",\n source,\n );\n }\n // 区分空数据和有效内容,空集合不创建可绘制资源。\n if (data.geometry.length === 0 || data.index.length === 0) {\n this.warn(\"EMPTY_GEOMETRY_\" + name, geometryId, name + \" 不包含可绘制图元。\", source);\n }\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (\n GetInvalidNumberIndex(data.geometry) !== -1 ||\n GetInvalidNumberIndex(data.normal) !== -1 ||\n GetInvalidNumberIndex(data.uv) !== -1\n ) {\n this.error(\"GEOMETRY_NUMBER_\" + name, geometryId, name + \" 包含 NaN 或 Infinity。\", source);\n }\n // 逐项处理 data.index,保持集合中的既定顺序。\n for (const index of data.index) {\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (index >= vertexCount) {\n this.error(\n \"GEOMETRY_INDEX_RANGE_\" + name,\n geometryId,\n name + \" 存在越界索引 \" + String(index) + \"。\",\n source,\n );\n break;\n }\n }\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (requireMiterScale && data.miterScale === undefined) {\n this.error(\"GEOMETRY_MITER_MISSING_\" + name, geometryId, name + \" 缺少 miterScale。\", source);\n } else if (data.miterScale !== undefined) {\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (data.miterScale.length !== vertexCount) {\n this.error(\n \"GEOMETRY_MITER_COUNT_\" + name,\n geometryId,\n name + \" miterScale 数量必须与顶点数量一致。\",\n source,\n );\n }\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (GetInvalidNumberIndex(data.miterScale) !== -1) {\n this.error(\n \"GEOMETRY_MITER_NUMBER_\" + name,\n geometryId,\n name + \" miterScale 包含 NaN 或 Infinity。\",\n source,\n );\n }\n }\n }\n /**\n * 输出一次开发模式警告。\n * @param code 问题代码\n * @param objectId 对象 ID\n * @param message 问题说明\n * @param source 关联对象\n * @example\n * this.warn(code, objectId, message, source);\n * @returns 无返回值\n */\n private warn(code: string, objectId: number, message: string, source: object): void {\n const key: string = \"warn_\" + code + \"_\" + String(objectId);\n // 相同对象的同类诊断只报告一次,避免逐帧刷屏。\n if (!this.reported.has(key)) {\n this.reported.add(key);\n console.warn(\"[BPLineJS:\" + code + \"] \" + message, source);\n } else {\n return;\n }\n }\n /**\n * 输出一次开发模式错误。\n * @param code 问题代码\n * @param objectId 对象 ID\n * @param message 问题说明\n * @param source 关联对象\n * @example\n * this.error(code, objectId, message, source);\n * @returns 无返回值\n */\n private error(code: string, objectId: number, message: string, source: object): void {\n const key: string = \"error_\" + code + \"_\" + String(objectId);\n // 相同对象的同类诊断只报告一次,避免逐帧刷屏。\n if (!this.reported.has(key)) {\n this.reported.add(key);\n console.error(\"[BPLineJS:\" + code + \"] \" + message, source);\n } else {\n return;\n }\n }\n}\n/**\n * 获取数组中首个非有限数索引。\n * @param data 待检查数值数组\n * @example\n * GetInvalidNumberIndex(data);\n * @returns 非有限数索引,全部有效时返回 -1\n */\nconst GetInvalidNumberIndex = (data: ArrayLike<number>): number => {\n // 按索引顺序处理三角面,保持绕序及属性下标一致。\n for (let index = 0; index < data.length; index += 1) {\n // 校验数值有效性,防止非有限值进入几何或 GPU 数据。\n if (Number.isFinite(data[index]) === false) {\n return index;\n }\n }\n return -1;\n};\nexport default DevelopmentValidator;\nexport type { DevelopmentValidatorLike };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DevelopmentValidator.js","names":[],"sources":["../../src/scripts/Render/DevelopmentValidator.ts"],"sourcesContent":["import IMesh from \"../IMesh\";\nimport type Camera from \"../Camera\";\nimport type { GeoData, GeoPartDataLike } from \"../Geometry\";\nimport type { Material2d } from \"../global-types\";\nimport type { MeshLike } from \"../Mesh\";\ninterface DevelopmentValidatorLike {\n /**\n * 检查 Camera 是否能生成有效矩阵与 Uniform。\n * @param camera 当前 Camera\n * @example\n * developmentValidator.validateCamera(camera);\n * @returns 无返回值\n */\n validateCamera(camera: Camera): void;\n /**\n * 检查材质样式数据和分区配置。\n * @param material 当前材质\n * @example\n * developmentValidator.validateMaterial(material);\n * @returns 无返回值\n */\n validateMaterial(material: Material2d): void;\n /**\n * 检查 Geometry 各缓冲分区的长度、有限值和索引范围。\n * @param geometry 当前 Geometry\n * @example\n * developmentValidator.validateGeometry(geometry);\n * @returns 无返回值\n */\n validateGeometry(geometry: GeoData): void;\n /**\n * 检查 Mesh 的 Geometry 与 Material 是否能组成目标边框绘制模式。\n * @param mesh 当前 Mesh\n * @example\n * developmentValidator.validateMesh(mesh);\n * @returns 无返回值\n */\n validateMesh(mesh: MeshLike): void;\n}\n/**\n * 开发模式 GPUBuffer 数据与渲染配置校验器。\n * 只报告 TypeScript 无法约束的运行时数据问题,并按对象和问题类型去重。\n * @class\n */\nclass DevelopmentValidator implements DevelopmentValidatorLike {\n private readonly reported: Set<string> = new Set<string>();\n /**\n * 检查 Camera 是否能生成有效矩阵与 Uniform。\n * @param camera 当前 Camera\n * @example\n * developmentValidator.validateCamera(camera);\n * @returns 无返回值\n */\n public validateCamera(camera: Camera): void {\n // 校验数值有效性,防止非有限值进入几何或 GPU 数据。\n if (!Number.isFinite(camera.width) || camera.width <= 0) {\n this.error(\"CAMERA_WIDTH\", camera.id, \"Camera width 必须是大于 0 的有限数。\", camera);\n }\n // 校验数值有效性,防止非有限值进入几何或 GPU 数据。\n if (!Number.isFinite(camera.height) || camera.height <= 0) {\n this.error(\"CAMERA_HEIGHT\", camera.id, \"Camera height 必须是大于 0 的有限数。\", camera);\n }\n // 校验数值有效性,防止非有限值进入几何或 GPU 数据。\n if (!Number.isFinite(camera.zoom) || camera.zoom <= 0) {\n this.error(\"CAMERA_ZOOM\", camera.id, \"Camera zoom 必须是大于 0 的有限数。\", camera);\n }\n }\n /**\n * 检查材质样式数据和分区配置。\n * @param material 当前材质\n * @example\n * developmentValidator.validateMaterial(material);\n * @returns 无返回值\n */\n public validateMaterial(material: Material2d): void {\n // 区分深度和透明状态,保持当前阶段的遮挡与混合约定。\n if (material.transparent && material.depthWrite) {\n this.warn(\n \"TRANSPARENT_DEPTH_WRITE\",\n material.id,\n \"透明混合同时写入深度可能挡住后方透明片元;通常使用 depthTest=true、depthWrite=false。\",\n material,\n );\n }\n // 区分深度和透明状态,保持当前阶段的遮挡与混合约定。\n if (!material.depthTest && material.depthWrite) {\n this.warn(\n \"DEPTH_WRITE_WITHOUT_TEST\",\n material.id,\n \"已关闭深度测试但仍写深度;它不会检查场景遮挡,通常 UI 两项都关闭。\",\n material,\n );\n }\n const { solid, wireframe, edge, points } = material.style;\n // 按轮廓顺序处理节点,使顶点属性与生成索引保持对应。\n for (const part of [solid, wireframe, edge, points]) {\n // 校验数值有效性,防止非有限值进入几何或 GPU 数据。\n if (!Number.isFinite(part.opacity) || part.opacity < 0 || part.opacity > 1) {\n this.error(\n \"STYLE_OPACITY_\" + part.area,\n material.id,\n \"分区透明度必须位于 0 到 1。\",\n material,\n );\n }\n }\n // 按轮廓顺序处理节点,使顶点属性与生成索引保持对应。\n for (const part of [solid, edge, points]) {\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (part.texture !== undefined && !part.enabled) {\n this.warn(\n \"UNUSED_TEXTURE_\" + part.area,\n material.id,\n \"分区已设置贴图,但 enabled 尚未开启。\",\n material,\n );\n }\n }\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (points.enabled && !points.vertices && !points.midpoints) {\n this.warn(\n \"EMPTY_POINT_SELECTION\",\n material.id,\n \"关键点已开启,但未选择顶点或边中点;不会生成点型。\",\n material,\n );\n }\n }\n /**\n * 检查 Geometry 各缓冲分区的长度、有限值和索引范围。\n * @param geometry 当前 Geometry\n * @example\n * developmentValidator.validateGeometry(geometry);\n * @returns 无返回值\n */\n public validateGeometry(geometry: GeoData): void {\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (geometry.geometry !== undefined) {\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (\n geometry.normal === undefined ||\n geometry.uv === undefined ||\n geometry.index === undefined ||\n geometry.vertexType === undefined ||\n geometry.position === undefined ||\n geometry.miterScale === undefined\n ) {\n this.error(\n \"GEOMETRY_MISSING\",\n geometry.id,\n \"合并三角面缺少 normal、uv、index、vertexType、position 或 miterScale。\",\n geometry,\n );\n } else {\n const vertexCount: number = geometry.geometry.length / 2;\n this.validatePart(\n geometry.id,\n \"geometry\",\n {\n geometry: geometry.geometry,\n normal: geometry.normal,\n uv: geometry.uv,\n index: geometry.index,\n miterScale: geometry.miterScale,\n },\n 3,\n true,\n geometry,\n );\n /**\n * 检查合并几何中的顶点用途是否属于支持的三种类型。\n * @param value 本次输入值\n * @example\n * HasInvalidVertexType(value);\n * @returns 当前类型值是否不受支持。\n */\n const HasInvalidVertexType = (value: number): boolean =>\n value !== 0 && value !== 0.5 && value !== 1;\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (\n geometry.vertexType.length !== vertexCount ||\n geometry.vertexType.some(HasInvalidVertexType)\n ) {\n this.error(\n \"VERTEX_TYPE\",\n geometry.id,\n \"vertexType 必须逐顶点对应,且只能为 0、0.5、1。\",\n geometry,\n );\n }\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (\n geometry.position.length !== geometry.geometry.length ||\n GetInvalidNumberIndex(geometry.position) !== -1\n ) {\n this.error(\n \"GEOMETRY_POSITION\",\n geometry.id,\n \"position 必须与 geometry 等长且全部为有限数。\",\n geometry,\n );\n }\n // 按顶点顺序写入坐标及配套属性,保持各数组长度一致。\n for (let offset: number = 0; offset < geometry.index.length; offset += 3) {\n const type: number = geometry.vertexType[geometry.index[offset]];\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (\n geometry.vertexType[geometry.index[offset + 1]] !== type ||\n geometry.vertexType[geometry.index[offset + 2]] !== type\n ) {\n this.error(\n \"TRIANGLE_VERTEX_TYPE\",\n geometry.id,\n \"一个三角面的三个顶点必须属于同一 vertexType。\",\n geometry,\n );\n break;\n }\n }\n }\n }\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (geometry.linePoints !== undefined) {\n this.validatePart(geometry.id, \"linePoints\", geometry.linePoints, 2, false, geometry);\n }\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (geometry.style.wireframe.enabled && geometry.linePoints === undefined) {\n this.error(\n \"WIREFRAME_DATA\",\n geometry.id,\n \"Geometry wireframe 已开启,但没有生成 linePoints。\",\n geometry,\n );\n }\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (\n geometry.style.edge.uvRepeat !== 1 &&\n !geometry.style.wireframe.enabled &&\n !geometry.style.edge.enabled\n ) {\n this.warn(\"UNUSED_UV_REPEAT\", geometry.id, \"uvRepeat 只作用于边框,当前值不会被使用。\", geometry);\n }\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (geometry.uniformData.length !== 4 || GetInvalidNumberIndex(geometry.uniformData) !== -1) {\n this.error(\n \"GEOMETRY_UNIFORM\",\n geometry.id,\n \"Geometry uniformData 必须包含 4 个有限数。\",\n geometry,\n );\n }\n }\n /**\n * 检查 Mesh 的 Geometry 与 Material 是否能组成目标边框绘制模式。\n * @param mesh 当前 Mesh\n * @example\n * developmentValidator.validateMesh(mesh);\n * @returns 无返回值\n */\n public validateMesh(mesh: MeshLike): void {\n const geometry = mesh.data;\n const material = mesh.material;\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (geometry !== undefined && material !== undefined) {\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (geometry.style.solid.enabled && ![\"Rect2D\", \"Poly2D\", \"NGon2D\", \"Text\"].includes(geometry.type)) {\n this.error(\"UNSUPPORTED_SOLID_TYPE\", mesh.id, \"未提供该类型的实体面 Shader。\", mesh);\n }\n // 区分输入数据形态,使用与实际类型匹配的处理方式。\n if (!(mesh instanceof IMesh)) {\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if ([\"Poly2D\", \"NGon2D\", \"Text\"].includes(geometry.type) && material.style.solid.borderWidth > 0) {\n this.warn(\n \"UNSUPPORTED_SDF_BORDER\",\n mesh.id,\n \"Poly2D/NGon2D/Text 不使用矩形 SDF 边框,请启用 edge 分区。\",\n mesh,\n );\n }\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (\n geometry.type === \"Poly2D\" &&\n (geometry.style.join.type !== material.style.join.type ||\n geometry.style.join.seg !== material.style.join.seg)\n ) {\n this.warn(\n \"POLY_JOIN_STYLE\",\n mesh.id,\n \"连接拓扑由 Geometry.style.join 生成,建议几何与材质共享 Style。\",\n mesh,\n );\n }\n // 按轮廓顺序处理节点,使顶点属性与生成索引保持对应。\n for (const area of [\"solid\", \"wireframe\", \"edge\", \"points\"] as const) {\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (geometry.style[area].enabled !== material.style[area].enabled) {\n this.warn(\n \"STYLE_ENABLED_\" + area,\n mesh.id,\n \"几何与材质的分区开关不同;建议引用同一个 Style。\",\n mesh,\n );\n }\n }\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (\n geometry.style !== material.style &&\n geometry.style.edge.enabled &&\n (geometry.style.edge.width !== material.style.edge.width ||\n geometry.style.edge.borderAlign !== material.style.edge.borderAlign)\n ) {\n this.warn(\n \"STYLE_EDGE_PARAMETERS\",\n mesh.id,\n \"几何与材质的实体边框参数不同;几何宽度参与生成,建议共享 Style。\",\n mesh,\n );\n }\n } else {\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (geometry.style.wireframe.enabled && mesh.count > 1) {\n this.warn(\n \"IMESH_NATIVE_LINES\",\n mesh.id,\n \"原生 line-list 使用逐实例顺序回退;只用三角面时才合并为一次绘制。\",\n mesh,\n );\n }\n // 遍历当前缓存条目,按实际引用关系处理资源。\n for (const raw of mesh.raws.map.values()) {\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if ([\"Poly2D\", \"NGon2D\", \"Text\"].includes(geometry.type) && raw.style.solid.borderWidth > 0) {\n this.warn(\n \"UNSUPPORTED_SDF_BORDER\",\n mesh.id,\n \"Poly2D/NGon2D/Text 不使用矩形 SDF 边框,请启用 edge 分区。\",\n mesh,\n );\n }\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (\n geometry.type === \"Poly2D\" &&\n (raw.style.join.type !== geometry.style.join.type ||\n raw.style.join.seg !== geometry.style.join.seg)\n ) {\n this.warn(\n \"IMESH_JOIN_TEMPLATE\",\n mesh.id,\n \"连接类型和精度由 Geometry.style.join 决定,Raw 不生成独立拓扑。\",\n mesh,\n );\n }\n // 按轮廓顺序处理节点,使顶点属性与生成索引保持对应。\n for (const area of [\"solid\", \"wireframe\", \"edge\", \"points\"] as const) {\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (raw.style[area].enabled && !geometry.style[area].enabled) {\n this.warn(\n \"IMESH_MISSING_\" + area,\n mesh.id,\n \"实例启用了模板未生成的 \" + area + \" 分区;请先配置 Geometry.style。\",\n mesh,\n );\n }\n }\n // 按轮廓顺序处理节点,使顶点属性与生成索引保持对应。\n for (const area of [\"solid\", \"edge\", \"points\"] as const) {\n // 处理转向与退化边界,避免零面积三角面或不稳定法线。\n if (\n raw.style[area].addressModeU !== material.style[area].addressModeU ||\n raw.style[area].addressModeV !== material.style[area].addressModeV\n ) {\n this.warn(\n \"IMESH_SAMPLER_\" + area,\n mesh.id,\n \"一次绘制共用 Material.style 的采样器,Raw.style 的寻址方式不单独生效。\",\n mesh,\n );\n }\n }\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (\n raw.style.edge.enabled &&\n (raw.style.edge.width !== geometry.style.edge.width ||\n raw.style.edge.uvRepeat !== geometry.style.edge.uvRepeat)\n ) {\n this.warn(\n \"IMESH_EDGE_TEMPLATE\",\n mesh.id,\n \"实例实体边框宽度和 UV Repeat 由共用 Geometry.style 生成,Raw 不重建独立几何。\",\n mesh,\n );\n }\n // 只为启用的样式区域生成数据,关闭区域不占用额外几何。\n if (\n raw.style.points.enabled &&\n (raw.style.points.radius !== geometry.style.points.radius ||\n raw.style.points.segments !== geometry.style.points.segments ||\n raw.style.points.vertices !== geometry.style.points.vertices ||\n raw.style.points.midpoints !== geometry.style.points.midpoints ||\n raw.style.points.minPointsLength !== geometry.style.points.minPointsLength ||\n raw.style.points.minEdgePointsLength !==\n geometry.style.points.minEdgePointsLength)\n ) {\n this.warn(\n \"IMESH_POINTS_TEMPLATE\",\n mesh.id,\n \"关键点半径、段数、选择规则由共用 Geometry.style 决定。\",\n mesh,\n );\n }\n }\n return;\n }\n } else {\n this.error(\"MESH_RESOURCE\", mesh.id, \"Mesh 缺少 Geometry 或 Material。\", mesh);\n return;\n }\n }\n /**\n * 检查单个几何缓冲分区。\n * @param geometryId Geometry ID\n * @param name 缓冲分区名\n * @param data CPU 几何数据\n * @param primitiveSize 每个图元使用的索引数\n * @param requireMiterScale 是否要求 miterScale\n * @param source 日志附带对象\n * @example\n * this.validatePart(geometryId, name, data, primitiveSize, requireMiterScale, source);\n * @returns 无返回值\n */\n private validatePart(\n geometryId: number,\n name: string,\n data: GeoPartDataLike,\n primitiveSize: 2 | 3,\n requireMiterScale: boolean,\n source: object,\n ): void {\n const vertexCount: number = data.geometry.length / 2;\n // 区分空数据和有效内容,空集合不创建可绘制资源。\n if (data.geometry.length % 2 !== 0) {\n this.error(\n \"GEOMETRY_VERTEX_\" + name,\n geometryId,\n name + \" 顶点数组长度必须是 2 的倍数。\",\n source,\n );\n }\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (data.normal.length !== data.geometry.length) {\n this.error(\"GEOMETRY_NORMAL_\" + name, geometryId, name + \" 法线数量必须与顶点数量一致。\", source);\n }\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (data.uv.length !== data.geometry.length) {\n this.error(\"GEOMETRY_UV_\" + name, geometryId, name + \" UV 数量必须与顶点数量一致。\", source);\n }\n // 区分空数据和有效内容,空集合不创建可绘制资源。\n if (data.index.length % primitiveSize !== 0) {\n this.error(\n \"GEOMETRY_INDEX_COUNT_\" + name,\n geometryId,\n name + \" 索引数量与图元类型不匹配。\",\n source,\n );\n }\n // 区分空数据和有效内容,空集合不创建可绘制资源。\n if (data.geometry.length === 0 || data.index.length === 0) {\n this.warn(\"EMPTY_GEOMETRY_\" + name, geometryId, name + \" 不包含可绘制图元。\", source);\n }\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (\n GetInvalidNumberIndex(data.geometry) !== -1 ||\n GetInvalidNumberIndex(data.normal) !== -1 ||\n GetInvalidNumberIndex(data.uv) !== -1\n ) {\n this.error(\"GEOMETRY_NUMBER_\" + name, geometryId, name + \" 包含 NaN 或 Infinity。\", source);\n }\n // 逐项处理 data.index,保持集合中的既定顺序。\n for (const index of data.index) {\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (index >= vertexCount) {\n this.error(\n \"GEOMETRY_INDEX_RANGE_\" + name,\n geometryId,\n name + \" 存在越界索引 \" + String(index) + \"。\",\n source,\n );\n break;\n }\n }\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (requireMiterScale && data.miterScale === undefined) {\n this.error(\"GEOMETRY_MITER_MISSING_\" + name, geometryId, name + \" 缺少 miterScale。\", source);\n } else if (data.miterScale !== undefined) {\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (data.miterScale.length !== vertexCount) {\n this.error(\n \"GEOMETRY_MITER_COUNT_\" + name,\n geometryId,\n name + \" miterScale 数量必须与顶点数量一致。\",\n source,\n );\n }\n // 检查顶点属性长度、数值和索引范围,提前报告不匹配的几何数据。\n if (GetInvalidNumberIndex(data.miterScale) !== -1) {\n this.error(\n \"GEOMETRY_MITER_NUMBER_\" + name,\n geometryId,\n name + \" miterScale 包含 NaN 或 Infinity。\",\n source,\n );\n }\n }\n }\n /**\n * 输出一次开发模式警告。\n * @param code 问题代码\n * @param objectId 对象 ID\n * @param message 问题说明\n * @param source 关联对象\n * @example\n * this.warn(code, objectId, message, source);\n * @returns 无返回值\n */\n private warn(code: string, objectId: number, message: string, source: object): void {\n const key: string = \"warn_\" + code + \"_\" + String(objectId);\n // 相同对象的同类诊断只报告一次,避免逐帧刷屏。\n if (!this.reported.has(key)) {\n this.reported.add(key);\n console.warn(\"[BPLineJS:\" + code + \"] \" + message, source);\n } else {\n return;\n }\n }\n /**\n * 输出一次开发模式错误。\n * @param code 问题代码\n * @param objectId 对象 ID\n * @param message 问题说明\n * @param source 关联对象\n * @example\n * this.error(code, objectId, message, source);\n * @returns 无返回值\n */\n private error(code: string, objectId: number, message: string, source: object): void {\n const key: string = \"error_\" + code + \"_\" + String(objectId);\n // 相同对象的同类诊断只报告一次,避免逐帧刷屏。\n if (!this.reported.has(key)) {\n this.reported.add(key);\n console.error(\"[BPLineJS:\" + code + \"] \" + message, source);\n } else {\n return;\n }\n }\n}\n/**\n * 获取数组中首个非有限数索引。\n * @param data 待检查数值数组\n * @example\n * GetInvalidNumberIndex(data);\n * @returns 非有限数索引,全部有效时返回 -1\n */\nconst GetInvalidNumberIndex = (data: ArrayLike<number>): number => {\n // 按索引顺序处理三角面,保持绕序及属性下标一致。\n for (let index = 0; index < data.length; index += 1) {\n // 校验数值有效性,防止非有限值进入几何或 GPU 数据。\n if (Number.isFinite(data[index]) === false) {\n return index;\n }\n }\n return -1;\n};\nexport default DevelopmentValidator;\nexport type { DevelopmentValidatorLike };\n"],"mappings":"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"}
|
|
@@ -244,6 +244,10 @@ var PipelineManager = class {
|
|
|
244
244
|
vertex: material.polyVertexShader,
|
|
245
245
|
fragment: material.polyFragmentShader
|
|
246
246
|
},
|
|
247
|
+
Text: {
|
|
248
|
+
vertex: material.polyVertexShader,
|
|
249
|
+
fragment: material.polyFragmentShader
|
|
250
|
+
},
|
|
247
251
|
NGon2D: {
|
|
248
252
|
vertex: material.ngonVertexShader,
|
|
249
253
|
fragment: material.ngonFragmentShader
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PipelineManager.js","names":[],"sources":["../../src/scripts/Render/PipelineManager.ts"],"sourcesContent":["import type { Material2d } from \"../global-types\";\nimport lineFragmentShader from \"../Material/wgsls/line/fragment.wgsl?raw\";\nimport lineVertexShader from \"../Material/wgsls/line/vertex.wgsl?raw\";\nimport type Scene from \"../Scene\";\nimport SamplerManager from \"./SamplerManager\";\nimport TextureManager from \"./TextureManager\";\nimport type { PipelineLike, PipelineTemplateLike } from \"./types\";\ninterface PipelineManagerLike {\n /**\n * 创建当前 Render 共用的 Layout 和后备贴图。\n * @param device 当前 Render 对应的 WebGPU 设备\n * @example\n * pipelineManager.initResources(device);\n * @returns 无返回值\n */\n initResources(device: GPUDevice): void;\n /**\n * 按 materialList 创建材质管线和采样器,几何类型组合由 Scene 维护。\n * @param scene 场景对象\n * @example\n * pipelineManager.draw(scene);\n * @returns 无返回值\n */\n draw(scene: Scene): void;\n}\n/**\n * 管理 BindGroupLayout、Sampler 和可复用的 GPURenderPipeline 模板。\n * @class\n */\nclass PipelineManager implements PipelineManagerLike {\n private readonly pipeline: PipelineLike;\n private readonly textureManager: TextureManager;\n private readonly samplerManager: SamplerManager;\n /**\n * 创建管线管理器。\n * @param pipeline 当前 Render 的共享管线资源\n * @param textureManager 贴图管理器\n * @param samplerManager Sampler 管理器\n * @example\n * const pipelineManager = new PipelineManager(pipeline, textureManager, samplerManager);\n * @returns 创建的 PipelineManager 对象。\n */\n public constructor(\n pipeline: PipelineLike,\n textureManager: TextureManager,\n samplerManager: SamplerManager,\n ) {\n this.pipeline = pipeline;\n this.textureManager = textureManager;\n this.samplerManager = samplerManager;\n }\n /**\n * 创建当前 Render 共用的 Layout 和后备贴图。\n * @param device 当前 Render 对应的 WebGPU 设备\n * @example\n * pipelineManager.initResources(device);\n * @returns 无返回值\n */\n public initResources(device: GPUDevice): void {\n this.pipeline.defaultBindGroupLayout = device.createBindGroupLayout({\n label: \"Default Matrix Bind Group Layout\",\n entries: [\n { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: \"read-only-storage\" } },\n { binding: 1, visibility: GPUShaderStage.VERTEX, buffer: { type: \"uniform\" } },\n { binding: 2, visibility: GPUShaderStage.VERTEX, buffer: { type: \"uniform\" } },\n {\n binding: 3,\n visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,\n buffer: { type: \"uniform\" },\n },\n {\n binding: 4,\n visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,\n buffer: { type: \"uniform\" },\n },\n { binding: 5, visibility: GPUShaderStage.VERTEX, buffer: { type: \"uniform\" } },\n ],\n });\n this.pipeline.baseMaterialBindGroupLayout = device.createBindGroupLayout({\n label: \"BaseMaterial Bind Group Layout\",\n entries: [\n {\n binding: 0,\n visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,\n buffer: { type: \"read-only-storage\" },\n },\n {\n binding: 1,\n visibility: GPUShaderStage.FRAGMENT,\n texture: { sampleType: \"float\", viewDimension: \"2d-array\" },\n },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: \"filtering\" } },\n // 三套贴图和寻址状态同时绑定,Shader 根据 vertexType 选择。\n {\n binding: 3,\n visibility: GPUShaderStage.FRAGMENT,\n texture: { sampleType: \"float\", viewDimension: \"2d-array\" },\n },\n { binding: 4, visibility: GPUShaderStage.FRAGMENT, sampler: { type: \"filtering\" } },\n {\n binding: 5,\n visibility: GPUShaderStage.FRAGMENT,\n texture: { sampleType: \"float\", viewDimension: \"2d-array\" },\n },\n { binding: 6, visibility: GPUShaderStage.FRAGMENT, sampler: { type: \"filtering\" } },\n ],\n });\n this.pipeline.fallbackTexture = this.textureManager.createFallbackTexture(device);\n }\n /**\n * 按 materialList 创建材质管线和采样器,几何类型组合由 Scene 维护。\n * @param scene 场景对象\n * @example\n * pipelineManager.draw(scene);\n * @returns 无返回值\n */\n public draw(scene: Scene): void {\n // 按去重后的材质列表处理共享状态。\n for (const material of scene.materialList) {\n this.samplerManager.get(material.style.solid.addressModeU, material.style.solid.addressModeV);\n this.samplerManager.get(material.style.edge.addressModeU, material.style.edge.addressModeV);\n this.samplerManager.get(material.style.points.addressModeU, material.style.points.addressModeV);\n // 逐项处理 scene.getMaterialGeometryTypes(material.id),保持集合中的既定顺序。\n for (const geometryType of scene.getMaterialGeometryTypes(material.id)) {\n const key: string = material.getPipelineKey(geometryType);\n // 检查集合中的关联关系,避免重复处理或遗漏引用。\n if (!this.pipeline.pipelineTemplates.has(key)) {\n const template = this.createPipelineTemplate(material, geometryType);\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (template !== undefined) {\n this.pipeline.pipelineTemplates.set(key, template);\n }\n } else {\n continue;\n }\n }\n }\n }\n /**\n * 按几何类型选择统一三角面 Shader,原生线框仍使用独立管线。\n * 未支持的几何不会误用矩形 Shader。\n * @param material 材质状态和源码集合\n * @param geometryType Geometry.type\n * @example\n * this.createPipelineTemplate(material, geometryType);\n * @returns 模板,设备或 Layout 未就绪时为 undefined\n */\n private createPipelineTemplate(\n material: Material2d,\n geometryType: string,\n ): PipelineTemplateLike | undefined {\n const device = this.pipeline.device;\n const format = this.pipeline.format;\n const defaultBindGroupLayout = this.pipeline.defaultBindGroupLayout;\n const materialBindGroupLayout = this.pipeline.baseMaterialBindGroupLayout;\n // GPU 设备及上下文就绪后才能创建或提交渲染资源。\n if (\n device !== undefined &&\n format !== undefined &&\n defaultBindGroupLayout !== undefined &&\n materialBindGroupLayout !== undefined\n ) {\n const layout: GPUPipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: [defaultBindGroupLayout, materialBindGroupLayout],\n });\n let blend: GPUBlendState | undefined;\n // 区分深度和透明状态,保持当前阶段的遮挡与混合约定。\n if (material.transparent) {\n blend = {\n color: { srcFactor: \"src-alpha\", dstFactor: \"one-minus-src-alpha\", operation: \"add\" },\n alpha: { srcFactor: \"one\", dstFactor: \"one-minus-src-alpha\", operation: \"add\" },\n };\n } else {\n blend = undefined;\n }\n /**\n * 创建一套图元管线,顶点缓冲槽位顺序由 attributes 决定。\n * @param label 调试标签\n * @param vertex 顶点源码\n * @param fragment 片元源码\n * @param topology 图元类型\n * @param attributes 顶点属性布局\n * @example\n * CreatePipeline(label, vertex, fragment, topology, attributes);\n * @returns 已创建的管线,源码未提供时为 undefined\n */\n const CreatePipeline = (\n label: string,\n vertex: string | undefined,\n fragment: string | undefined,\n topology: GPUPrimitiveTopology,\n attributes: GPUVertexBufferLayout[],\n ): GPURenderPipeline | undefined => {\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (vertex !== undefined && fragment !== undefined) {\n let depthCompare: GPUCompareFunction = \"always\";\n // 区分深度和透明状态,保持当前阶段的遮挡与混合约定。\n if (material.depthTest) {\n depthCompare = \"less-equal\";\n }\n let cullMode: GPUCullMode = material.cullMode;\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (topology === \"line-list\") {\n cullMode = \"none\";\n }\n return device.createRenderPipeline({\n label,\n layout,\n vertex: {\n module: device.createShaderModule({ code: vertex }),\n entryPoint: \"main\",\n buffers: attributes,\n },\n fragment: {\n module: device.createShaderModule({ code: fragment }),\n entryPoint: \"main\",\n targets: [{ format, blend }],\n },\n depthStencil: {\n format: \"depth32float\",\n depthWriteEnabled: material.depthWrite,\n // 同一实例的面/边框/点共享深度,equal 允许后绘制分区覆盖前面的分区。\n depthCompare,\n },\n primitive: {\n topology,\n // 现有 2D 几何采用顺时针三角面;line-list 不剔除面。\n frontFace: \"cw\",\n cullMode,\n },\n });\n } else {\n return undefined;\n }\n };\n /**\n * 为二维向量属性生成八字节步长的顶点缓冲布局。\n * @param location 顶点属性槽位\n * @example\n * CreateVectorAttribute(location);\n * @returns 指定槽位的顶点属性布局。\n */\n const CreateVectorAttribute = (location: number): GPUVertexBufferLayout => ({\n arrayStride: 8,\n attributes: [{ shaderLocation: location, offset: 0, format: \"float32x2\" }],\n });\n const vectorAttributes: GPUVertexBufferLayout[] = [0, 1, 2].map(CreateVectorAttribute);\n let renderPipeline: GPURenderPipeline | undefined;\n const shaders: Partial<\n Record<\n string,\n {\n vertex: string | undefined;\n fragment: string | undefined;\n }\n >\n > = {\n Rect2D: { vertex: material.rectVertexShader, fragment: material.rectFragmentShader },\n Poly2D: { vertex: material.polyVertexShader, fragment: material.polyFragmentShader },\n NGon2D: { vertex: material.ngonVertexShader, fragment: material.ngonFragmentShader },\n };\n const shader = shaders[geometryType];\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (shader !== undefined) {\n renderPipeline = CreatePipeline(\n \"BaseMaterial \" + geometryType + \" Pipeline\",\n shader.vertex,\n shader.fragment,\n \"triangle-list\",\n [\n ...vectorAttributes,\n { arrayStride: 4, attributes: [{ shaderLocation: 3, offset: 0, format: \"float32\" }] },\n {\n arrayStride: 8,\n attributes: [{ shaderLocation: 4, offset: 0, format: \"float32x2\" }],\n },\n { arrayStride: 4, attributes: [{ shaderLocation: 5, offset: 0, format: \"float32\" }] },\n ],\n );\n }\n const lineRenderPipeline = CreatePipeline(\n \"Line Pipeline\",\n lineVertexShader,\n lineFragmentShader,\n \"line-list\",\n [vectorAttributes[0], vectorAttributes[2]],\n );\n return {\n key: material.getPipelineKey(geometryType),\n renderPipeline,\n lineRenderPipeline,\n defaultBindGroupLayout,\n materialBindGroupLayout,\n };\n } else {\n return undefined;\n }\n }\n}\nexport default PipelineManager;\nexport type { PipelineManagerLike };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PipelineManager.js","names":[],"sources":["../../src/scripts/Render/PipelineManager.ts"],"sourcesContent":["import type { Material2d } from \"../global-types\";\nimport lineFragmentShader from \"../Material/wgsls/line/fragment.wgsl?raw\";\nimport lineVertexShader from \"../Material/wgsls/line/vertex.wgsl?raw\";\nimport type Scene from \"../Scene\";\nimport SamplerManager from \"./SamplerManager\";\nimport TextureManager from \"./TextureManager\";\nimport type { PipelineLike, PipelineTemplateLike } from \"./types\";\ninterface PipelineManagerLike {\n /**\n * 创建当前 Render 共用的 Layout 和后备贴图。\n * @param device 当前 Render 对应的 WebGPU 设备\n * @example\n * pipelineManager.initResources(device);\n * @returns 无返回值\n */\n initResources(device: GPUDevice): void;\n /**\n * 按 materialList 创建材质管线和采样器,几何类型组合由 Scene 维护。\n * @param scene 场景对象\n * @example\n * pipelineManager.draw(scene);\n * @returns 无返回值\n */\n draw(scene: Scene): void;\n}\n/**\n * 管理 BindGroupLayout、Sampler 和可复用的 GPURenderPipeline 模板。\n * @class\n */\nclass PipelineManager implements PipelineManagerLike {\n private readonly pipeline: PipelineLike;\n private readonly textureManager: TextureManager;\n private readonly samplerManager: SamplerManager;\n /**\n * 创建管线管理器。\n * @param pipeline 当前 Render 的共享管线资源\n * @param textureManager 贴图管理器\n * @param samplerManager Sampler 管理器\n * @example\n * const pipelineManager = new PipelineManager(pipeline, textureManager, samplerManager);\n * @returns 创建的 PipelineManager 对象。\n */\n public constructor(\n pipeline: PipelineLike,\n textureManager: TextureManager,\n samplerManager: SamplerManager,\n ) {\n this.pipeline = pipeline;\n this.textureManager = textureManager;\n this.samplerManager = samplerManager;\n }\n /**\n * 创建当前 Render 共用的 Layout 和后备贴图。\n * @param device 当前 Render 对应的 WebGPU 设备\n * @example\n * pipelineManager.initResources(device);\n * @returns 无返回值\n */\n public initResources(device: GPUDevice): void {\n this.pipeline.defaultBindGroupLayout = device.createBindGroupLayout({\n label: \"Default Matrix Bind Group Layout\",\n entries: [\n { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: \"read-only-storage\" } },\n { binding: 1, visibility: GPUShaderStage.VERTEX, buffer: { type: \"uniform\" } },\n { binding: 2, visibility: GPUShaderStage.VERTEX, buffer: { type: \"uniform\" } },\n {\n binding: 3,\n visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,\n buffer: { type: \"uniform\" },\n },\n {\n binding: 4,\n visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,\n buffer: { type: \"uniform\" },\n },\n { binding: 5, visibility: GPUShaderStage.VERTEX, buffer: { type: \"uniform\" } },\n ],\n });\n this.pipeline.baseMaterialBindGroupLayout = device.createBindGroupLayout({\n label: \"BaseMaterial Bind Group Layout\",\n entries: [\n {\n binding: 0,\n visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,\n buffer: { type: \"read-only-storage\" },\n },\n {\n binding: 1,\n visibility: GPUShaderStage.FRAGMENT,\n texture: { sampleType: \"float\", viewDimension: \"2d-array\" },\n },\n { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: \"filtering\" } },\n // 三套贴图和寻址状态同时绑定,Shader 根据 vertexType 选择。\n {\n binding: 3,\n visibility: GPUShaderStage.FRAGMENT,\n texture: { sampleType: \"float\", viewDimension: \"2d-array\" },\n },\n { binding: 4, visibility: GPUShaderStage.FRAGMENT, sampler: { type: \"filtering\" } },\n {\n binding: 5,\n visibility: GPUShaderStage.FRAGMENT,\n texture: { sampleType: \"float\", viewDimension: \"2d-array\" },\n },\n { binding: 6, visibility: GPUShaderStage.FRAGMENT, sampler: { type: \"filtering\" } },\n ],\n });\n this.pipeline.fallbackTexture = this.textureManager.createFallbackTexture(device);\n }\n /**\n * 按 materialList 创建材质管线和采样器,几何类型组合由 Scene 维护。\n * @param scene 场景对象\n * @example\n * pipelineManager.draw(scene);\n * @returns 无返回值\n */\n public draw(scene: Scene): void {\n // 按去重后的材质列表处理共享状态。\n for (const material of scene.materialList) {\n this.samplerManager.get(material.style.solid.addressModeU, material.style.solid.addressModeV);\n this.samplerManager.get(material.style.edge.addressModeU, material.style.edge.addressModeV);\n this.samplerManager.get(material.style.points.addressModeU, material.style.points.addressModeV);\n // 逐项处理 scene.getMaterialGeometryTypes(material.id),保持集合中的既定顺序。\n for (const geometryType of scene.getMaterialGeometryTypes(material.id)) {\n const key: string = material.getPipelineKey(geometryType);\n // 检查集合中的关联关系,避免重复处理或遗漏引用。\n if (!this.pipeline.pipelineTemplates.has(key)) {\n const template = this.createPipelineTemplate(material, geometryType);\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (template !== undefined) {\n this.pipeline.pipelineTemplates.set(key, template);\n }\n } else {\n continue;\n }\n }\n }\n }\n /**\n * 按几何类型选择统一三角面 Shader,原生线框仍使用独立管线。\n * 未支持的几何不会误用矩形 Shader。\n * @param material 材质状态和源码集合\n * @param geometryType Geometry.type\n * @example\n * this.createPipelineTemplate(material, geometryType);\n * @returns 模板,设备或 Layout 未就绪时为 undefined\n */\n private createPipelineTemplate(\n material: Material2d,\n geometryType: string,\n ): PipelineTemplateLike | undefined {\n const device = this.pipeline.device;\n const format = this.pipeline.format;\n const defaultBindGroupLayout = this.pipeline.defaultBindGroupLayout;\n const materialBindGroupLayout = this.pipeline.baseMaterialBindGroupLayout;\n // GPU 设备及上下文就绪后才能创建或提交渲染资源。\n if (\n device !== undefined &&\n format !== undefined &&\n defaultBindGroupLayout !== undefined &&\n materialBindGroupLayout !== undefined\n ) {\n const layout: GPUPipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: [defaultBindGroupLayout, materialBindGroupLayout],\n });\n let blend: GPUBlendState | undefined;\n // 区分深度和透明状态,保持当前阶段的遮挡与混合约定。\n if (material.transparent) {\n blend = {\n color: { srcFactor: \"src-alpha\", dstFactor: \"one-minus-src-alpha\", operation: \"add\" },\n alpha: { srcFactor: \"one\", dstFactor: \"one-minus-src-alpha\", operation: \"add\" },\n };\n } else {\n blend = undefined;\n }\n /**\n * 创建一套图元管线,顶点缓冲槽位顺序由 attributes 决定。\n * @param label 调试标签\n * @param vertex 顶点源码\n * @param fragment 片元源码\n * @param topology 图元类型\n * @param attributes 顶点属性布局\n * @example\n * CreatePipeline(label, vertex, fragment, topology, attributes);\n * @returns 已创建的管线,源码未提供时为 undefined\n */\n const CreatePipeline = (\n label: string,\n vertex: string | undefined,\n fragment: string | undefined,\n topology: GPUPrimitiveTopology,\n attributes: GPUVertexBufferLayout[],\n ): GPURenderPipeline | undefined => {\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (vertex !== undefined && fragment !== undefined) {\n let depthCompare: GPUCompareFunction = \"always\";\n // 区分深度和透明状态,保持当前阶段的遮挡与混合约定。\n if (material.depthTest) {\n depthCompare = \"less-equal\";\n }\n let cullMode: GPUCullMode = material.cullMode;\n // 按几何或图元类型选择对应实现,不混用不同模板的规则。\n if (topology === \"line-list\") {\n cullMode = \"none\";\n }\n return device.createRenderPipeline({\n label,\n layout,\n vertex: {\n module: device.createShaderModule({ code: vertex }),\n entryPoint: \"main\",\n buffers: attributes,\n },\n fragment: {\n module: device.createShaderModule({ code: fragment }),\n entryPoint: \"main\",\n targets: [{ format, blend }],\n },\n depthStencil: {\n format: \"depth32float\",\n depthWriteEnabled: material.depthWrite,\n // 同一实例的面/边框/点共享深度,equal 允许后绘制分区覆盖前面的分区。\n depthCompare,\n },\n primitive: {\n topology,\n // 现有 2D 几何采用顺时针三角面;line-list 不剔除面。\n frontFace: \"cw\",\n cullMode,\n },\n });\n } else {\n return undefined;\n }\n };\n /**\n * 为二维向量属性生成八字节步长的顶点缓冲布局。\n * @param location 顶点属性槽位\n * @example\n * CreateVectorAttribute(location);\n * @returns 指定槽位的顶点属性布局。\n */\n const CreateVectorAttribute = (location: number): GPUVertexBufferLayout => ({\n arrayStride: 8,\n attributes: [{ shaderLocation: location, offset: 0, format: \"float32x2\" }],\n });\n const vectorAttributes: GPUVertexBufferLayout[] = [0, 1, 2].map(CreateVectorAttribute);\n let renderPipeline: GPURenderPipeline | undefined;\n const shaders: Partial<\n Record<\n string,\n {\n vertex: string | undefined;\n fragment: string | undefined;\n }\n >\n > = {\n Rect2D: { vertex: material.rectVertexShader, fragment: material.rectFragmentShader },\n Poly2D: { vertex: material.polyVertexShader, fragment: material.polyFragmentShader },\n Text: { vertex: material.polyVertexShader, fragment: material.polyFragmentShader },\n NGon2D: { vertex: material.ngonVertexShader, fragment: material.ngonFragmentShader },\n };\n const shader = shaders[geometryType];\n // 存在有效引用时处理对应资源,缺省情况由备用分支接管。\n if (shader !== undefined) {\n renderPipeline = CreatePipeline(\n \"BaseMaterial \" + geometryType + \" Pipeline\",\n shader.vertex,\n shader.fragment,\n \"triangle-list\",\n [\n ...vectorAttributes,\n { arrayStride: 4, attributes: [{ shaderLocation: 3, offset: 0, format: \"float32\" }] },\n {\n arrayStride: 8,\n attributes: [{ shaderLocation: 4, offset: 0, format: \"float32x2\" }],\n },\n { arrayStride: 4, attributes: [{ shaderLocation: 5, offset: 0, format: \"float32\" }] },\n ],\n );\n }\n const lineRenderPipeline = CreatePipeline(\n \"Line Pipeline\",\n lineVertexShader,\n lineFragmentShader,\n \"line-list\",\n [vectorAttributes[0], vectorAttributes[2]],\n );\n return {\n key: material.getPipelineKey(geometryType),\n renderPipeline,\n lineRenderPipeline,\n defaultBindGroupLayout,\n materialBindGroupLayout,\n };\n } else {\n return undefined;\n }\n }\n}\nexport default PipelineManager;\nexport type { PipelineManagerLike };\n"],"mappings":";;;;;;;AA6BA,IAAM,kBAAN,MAAqD;CACjD;CACA;CACA;;;;;;;;;;CAUA,YACI,UACA,gBACA,gBACF;EACE,KAAK,WAAW;EAChB,KAAK,iBAAiB;EACtB,KAAK,iBAAiB;CAC1B;;;;;;;;CAQA,cAAqB,QAAyB;EAC1C,KAAK,SAAS,yBAAyB,OAAO,sBAAsB;GAChE,OAAO;GACP,SAAS;IACL;KAAE,SAAS;KAAG,YAAY,eAAe;KAAQ,QAAQ,EAAE,MAAM,oBAAoB;IAAE;IACvF;KAAE,SAAS;KAAG,YAAY,eAAe;KAAQ,QAAQ,EAAE,MAAM,UAAU;IAAE;IAC7E;KAAE,SAAS;KAAG,YAAY,eAAe;KAAQ,QAAQ,EAAE,MAAM,UAAU;IAAE;IAC7E;KACI,SAAS;KACT,YAAY,eAAe,SAAS,eAAe;KACnD,QAAQ,EAAE,MAAM,UAAU;IAC9B;IACA;KACI,SAAS;KACT,YAAY,eAAe,SAAS,eAAe;KACnD,QAAQ,EAAE,MAAM,UAAU;IAC9B;IACA;KAAE,SAAS;KAAG,YAAY,eAAe;KAAQ,QAAQ,EAAE,MAAM,UAAU;IAAE;GACjF;EACJ,CAAC;EACD,KAAK,SAAS,8BAA8B,OAAO,sBAAsB;GACrE,OAAO;GACP,SAAS;IACL;KACI,SAAS;KACT,YAAY,eAAe,SAAS,eAAe;KACnD,QAAQ,EAAE,MAAM,oBAAoB;IACxC;IACA;KACI,SAAS;KACT,YAAY,eAAe;KAC3B,SAAS;MAAE,YAAY;MAAS,eAAe;KAAW;IAC9D;IACA;KAAE,SAAS;KAAG,YAAY,eAAe;KAAU,SAAS,EAAE,MAAM,YAAY;IAAE;IAElF;KACI,SAAS;KACT,YAAY,eAAe;KAC3B,SAAS;MAAE,YAAY;MAAS,eAAe;KAAW;IAC9D;IACA;KAAE,SAAS;KAAG,YAAY,eAAe;KAAU,SAAS,EAAE,MAAM,YAAY;IAAE;IAClF;KACI,SAAS;KACT,YAAY,eAAe;KAC3B,SAAS;MAAE,YAAY;MAAS,eAAe;KAAW;IAC9D;IACA;KAAE,SAAS;KAAG,YAAY,eAAe;KAAU,SAAS,EAAE,MAAM,YAAY;IAAE;GACtF;EACJ,CAAC;EACD,KAAK,SAAS,kBAAkB,KAAK,eAAe,sBAAsB,MAAM;CACpF;;;;;;;;CAQA,KAAY,OAAoB;EAE5B,KAAK,MAAM,YAAY,MAAM,cAAc;GACvC,KAAK,eAAe,IAAI,SAAS,MAAM,MAAM,cAAc,SAAS,MAAM,MAAM,YAAY;GAC5F,KAAK,eAAe,IAAI,SAAS,MAAM,KAAK,cAAc,SAAS,MAAM,KAAK,YAAY;GAC1F,KAAK,eAAe,IAAI,SAAS,MAAM,OAAO,cAAc,SAAS,MAAM,OAAO,YAAY;GAE9F,KAAK,MAAM,gBAAgB,MAAM,yBAAyB,SAAS,EAAE,GAAG;IACpE,MAAM,MAAc,SAAS,eAAe,YAAY;IAExD,IAAI,CAAC,KAAK,SAAS,kBAAkB,IAAI,GAAG,GAAG;KAC3C,MAAM,WAAW,KAAK,uBAAuB,UAAU,YAAY;KAEnE,IAAI,aAAa,KAAA,GACb,KAAK,SAAS,kBAAkB,IAAI,KAAK,QAAQ;IAEzD,OACI;GAER;EACJ;CACJ;;;;;;;;;;CAUA,uBACI,UACA,cACgC;EAChC,MAAM,SAAS,KAAK,SAAS;EAC7B,MAAM,SAAS,KAAK,SAAS;EAC7B,MAAM,yBAAyB,KAAK,SAAS;EAC7C,MAAM,0BAA0B,KAAK,SAAS;EAE9C,IACI,WAAW,KAAA,KACX,WAAW,KAAA,KACX,2BAA2B,KAAA,KAC3B,4BAA4B,KAAA,GAC9B;GACE,MAAM,SAA4B,OAAO,qBAAqB,EAC1D,kBAAkB,CAAC,wBAAwB,uBAAuB,EACtE,CAAC;GACD,IAAI;GAEJ,IAAI,SAAS,aACT,QAAQ;IACJ,OAAO;KAAE,WAAW;KAAa,WAAW;KAAuB,WAAW;IAAM;IACpF,OAAO;KAAE,WAAW;KAAO,WAAW;KAAuB,WAAW;IAAM;GAClF;QAEA,QAAQ,KAAA;;;;;;;;;;;;GAaZ,MAAM,kBACF,OACA,QACA,UACA,UACA,eACgC;IAEhC,IAAI,WAAW,KAAA,KAAa,aAAa,KAAA,GAAW;KAChD,IAAI,eAAmC;KAEvC,IAAI,SAAS,WACT,eAAe;KAEnB,IAAI,WAAwB,SAAS;KAErC,IAAI,aAAa,aACb,WAAW;KAEf,OAAO,OAAO,qBAAqB;MAC/B;MACA;MACA,QAAQ;OACJ,QAAQ,OAAO,mBAAmB,EAAE,MAAM,OAAO,CAAC;OAClD,YAAY;OACZ,SAAS;MACb;MACA,UAAU;OACN,QAAQ,OAAO,mBAAmB,EAAE,MAAM,SAAS,CAAC;OACpD,YAAY;OACZ,SAAS,CAAC;QAAE;QAAQ;OAAM,CAAC;MAC/B;MACA,cAAc;OACV,QAAQ;OACR,mBAAmB,SAAS;OAE5B;MACJ;MACA,WAAW;OACP;OAEA,WAAW;OACX;MACJ;KACJ,CAAC;IACL,OACI;GAER;;;;;;;;GAQA,MAAM,yBAAyB,cAA6C;IACxE,aAAa;IACb,YAAY,CAAC;KAAE,gBAAgB;KAAU,QAAQ;KAAG,QAAQ;IAAY,CAAC;GAC7E;GACA,MAAM,mBAA4C;IAAC;IAAG;IAAG;GAAC,CAAC,CAAC,IAAI,qBAAqB;GACrF,IAAI;GAeJ,MAAM,SAAS;IALX,QAAQ;KAAE,QAAQ,SAAS;KAAkB,UAAU,SAAS;IAAmB;IACnF,QAAQ;KAAE,QAAQ,SAAS;KAAkB,UAAU,SAAS;IAAmB;IACnF,MAAM;KAAE,QAAQ,SAAS;KAAkB,UAAU,SAAS;IAAmB;IACjF,QAAQ;KAAE,QAAQ,SAAS;KAAkB,UAAU,SAAS;IAAmB;GAExE,EAAQ;GAEvB,IAAI,WAAW,KAAA,GACX,iBAAiB,eACb,kBAAkB,eAAe,aACjC,OAAO,QACP,OAAO,UACP,iBACA;IACI,GAAG;IACH;KAAE,aAAa;KAAG,YAAY,CAAC;MAAE,gBAAgB;MAAG,QAAQ;MAAG,QAAQ;KAAU,CAAC;IAAE;IACpF;KACI,aAAa;KACb,YAAY,CAAC;MAAE,gBAAgB;MAAG,QAAQ;MAAG,QAAQ;KAAY,CAAC;IACtE;IACA;KAAE,aAAa;KAAG,YAAY,CAAC;MAAE,gBAAgB;MAAG,QAAQ;MAAG,QAAQ;KAAU,CAAC;IAAE;GACxF,CACJ;GAEJ,MAAM,qBAAqB,eACvB,iBACA,gBACA,kBACA,aACA,CAAC,iBAAiB,IAAI,iBAAiB,EAAE,CAC7C;GACA,OAAO;IACH,KAAK,SAAS,eAAe,YAAY;IACzC;IACA;IACA;IACA;GACJ;EACJ,OACI;CAER;AACJ"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { TextGeometryData, TextOptions } from "bpmatrixjs/Geometry/Text";
|
|
2
|
+
/** 按指定字号和排版设置创建已注册字体的填充三角面。 */
|
|
3
|
+
type TextGeometryFactory = (text: string, fontSize?: number, options?: TextOptions) => TextGeometryData;
|
|
4
|
+
/** 按名称共享已加载字体,供后续文字几何直接使用。 */
|
|
5
|
+
declare class Font {
|
|
6
|
+
private static readonly _urls;
|
|
7
|
+
private static readonly _factories;
|
|
8
|
+
private static readonly _pending;
|
|
9
|
+
/**
|
|
10
|
+
* 已成功注册的字体名称与几何创建函数。返回同一个只读 Map 视图。
|
|
11
|
+
* @example
|
|
12
|
+
* const geometry = Font.map.get("MiSans")?.("啊");
|
|
13
|
+
* @returns 字体名称到几何创建函数的只读 Map。
|
|
14
|
+
*/
|
|
15
|
+
static get map(): ReadonlyMap<string, TextGeometryFactory>;
|
|
16
|
+
/**
|
|
17
|
+
* 下载并注册字体;同名同址的并发调用共用一次请求。
|
|
18
|
+
* @param fontFamily 供文字几何使用的注册名称。
|
|
19
|
+
* @param url TTF、OTF、WOFF 或 WOFF2 字体文件地址。
|
|
20
|
+
* @example
|
|
21
|
+
* const createGeometry = await Font.register("MiSans", fontUrl);
|
|
22
|
+
* const geometry = createGeometry("啊", 32);
|
|
23
|
+
* @returns 几何创建函数;下载或解析失败时拒绝 Promise。
|
|
24
|
+
*/
|
|
25
|
+
static register(fontFamily: string, url: string): Promise<TextGeometryFactory>;
|
|
26
|
+
/**
|
|
27
|
+
* 根据注册名称取得可调用的文字几何创建函数。
|
|
28
|
+
* @param fontFamily 已注册的字体名称。
|
|
29
|
+
* @example
|
|
30
|
+
* const geometry = Font.get("MiSans")("啊");
|
|
31
|
+
* @returns 创建填充三角面的函数。
|
|
32
|
+
*/
|
|
33
|
+
static get(fontFamily: string): TextGeometryFactory;
|
|
34
|
+
/**
|
|
35
|
+
* 查询字体是否已完成注册。
|
|
36
|
+
* @param fontFamily 注册名称。
|
|
37
|
+
* @example
|
|
38
|
+
* Font.has("MiSans");
|
|
39
|
+
* @returns 字体是否可用于文字几何。
|
|
40
|
+
*/
|
|
41
|
+
static has(fontFamily: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* 下载字体字节并提交到 BPMatrixJS 的字体表。
|
|
44
|
+
* @param fontFamily 注册名称。
|
|
45
|
+
* @param url 字体文件地址。
|
|
46
|
+
* @example
|
|
47
|
+
* await Font.register("MiSans", fontUrl);
|
|
48
|
+
* @returns 几何创建函数。
|
|
49
|
+
*/
|
|
50
|
+
private static load;
|
|
51
|
+
}
|
|
52
|
+
export default Font;
|
|
53
|
+
export type { TextGeometryFactory };
|
package/lib/Text/font.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { CreateTextGeometry, HasTextFont, RegisterTextFont } from "bpmatrixjs/Geometry/Text";
|
|
2
|
+
//#region src/scripts/Text/font.ts
|
|
3
|
+
/** 按名称共享已加载字体,供后续文字几何直接使用。 */
|
|
4
|
+
var Font = class Font {
|
|
5
|
+
static _urls = /* @__PURE__ */ new Map();
|
|
6
|
+
static _factories = /* @__PURE__ */ new Map();
|
|
7
|
+
static _pending = /* @__PURE__ */ new Map();
|
|
8
|
+
/**
|
|
9
|
+
* 已成功注册的字体名称与几何创建函数。返回同一个只读 Map 视图。
|
|
10
|
+
* @example
|
|
11
|
+
* const geometry = Font.map.get("MiSans")?.("啊");
|
|
12
|
+
* @returns 字体名称到几何创建函数的只读 Map。
|
|
13
|
+
*/
|
|
14
|
+
static get map() {
|
|
15
|
+
return Font._factories;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 下载并注册字体;同名同址的并发调用共用一次请求。
|
|
19
|
+
* @param fontFamily 供文字几何使用的注册名称。
|
|
20
|
+
* @param url TTF、OTF、WOFF 或 WOFF2 字体文件地址。
|
|
21
|
+
* @example
|
|
22
|
+
* const createGeometry = await Font.register("MiSans", fontUrl);
|
|
23
|
+
* const geometry = createGeometry("啊", 32);
|
|
24
|
+
* @returns 几何创建函数;下载或解析失败时拒绝 Promise。
|
|
25
|
+
*/
|
|
26
|
+
static register(fontFamily, url) {
|
|
27
|
+
if (fontFamily.trim().length === 0 || url.trim().length === 0) throw new RangeError("Font family and URL must not be empty.");
|
|
28
|
+
const registeredUrl = Font._urls.get(fontFamily);
|
|
29
|
+
if (registeredUrl !== void 0 && HasTextFont(fontFamily)) {
|
|
30
|
+
if (registeredUrl === url) return Promise.resolve(Font.get(fontFamily));
|
|
31
|
+
else throw new Error(`Font family is already registered from another URL: ${fontFamily}`);
|
|
32
|
+
}
|
|
33
|
+
const pendingFont = Font._pending.get(fontFamily);
|
|
34
|
+
if (pendingFont !== void 0) {
|
|
35
|
+
if (pendingFont.url === url) return pendingFont.promise;
|
|
36
|
+
else throw new Error(`Font family is already loading from another URL: ${fontFamily}`);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 请求结束时移除在途记录,使失败后能够再次尝试。
|
|
40
|
+
* @example
|
|
41
|
+
* await promise.finally(ClearPending);
|
|
42
|
+
* @returns 无返回值。
|
|
43
|
+
*/
|
|
44
|
+
const ClearPending = () => {
|
|
45
|
+
Font._pending.delete(fontFamily);
|
|
46
|
+
};
|
|
47
|
+
const promise = Font.load(fontFamily, url).finally(ClearPending);
|
|
48
|
+
Font._pending.set(fontFamily, {
|
|
49
|
+
url,
|
|
50
|
+
promise
|
|
51
|
+
});
|
|
52
|
+
return promise;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 根据注册名称取得可调用的文字几何创建函数。
|
|
56
|
+
* @param fontFamily 已注册的字体名称。
|
|
57
|
+
* @example
|
|
58
|
+
* const geometry = Font.get("MiSans")("啊");
|
|
59
|
+
* @returns 创建填充三角面的函数。
|
|
60
|
+
*/
|
|
61
|
+
static get(fontFamily) {
|
|
62
|
+
const createGeometry = Font._factories.get(fontFamily);
|
|
63
|
+
if (createGeometry !== void 0 && HasTextFont(fontFamily)) return createGeometry;
|
|
64
|
+
else throw new Error(`Font is not registered: ${fontFamily}`);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 查询字体是否已完成注册。
|
|
68
|
+
* @param fontFamily 注册名称。
|
|
69
|
+
* @example
|
|
70
|
+
* Font.has("MiSans");
|
|
71
|
+
* @returns 字体是否可用于文字几何。
|
|
72
|
+
*/
|
|
73
|
+
static has(fontFamily) {
|
|
74
|
+
return Font._factories.has(fontFamily) && HasTextFont(fontFamily);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 下载字体字节并提交到 BPMatrixJS 的字体表。
|
|
78
|
+
* @param fontFamily 注册名称。
|
|
79
|
+
* @param url 字体文件地址。
|
|
80
|
+
* @example
|
|
81
|
+
* await Font.register("MiSans", fontUrl);
|
|
82
|
+
* @returns 几何创建函数。
|
|
83
|
+
*/
|
|
84
|
+
static async load(fontFamily, url) {
|
|
85
|
+
const response = await fetch(url);
|
|
86
|
+
if (response.ok) {
|
|
87
|
+
const data = await response.arrayBuffer();
|
|
88
|
+
RegisterTextFont(fontFamily, data);
|
|
89
|
+
/**
|
|
90
|
+
* 使用当前已注册字体创建填充三角面,并允许每次设置字号与排版。
|
|
91
|
+
* @param text 支持换行的文字。
|
|
92
|
+
* @param fontSize 几何字号,默认 16。
|
|
93
|
+
* @param options 行距、字距及对齐方式。
|
|
94
|
+
* @example
|
|
95
|
+
* CreateGeometry("啊", 32);
|
|
96
|
+
* @returns 顶点、索引及排版度量。
|
|
97
|
+
*/
|
|
98
|
+
const CreateGeometry = (text, fontSize = 16, options = {}) => {
|
|
99
|
+
return CreateTextGeometry(text, fontSize, fontFamily, options);
|
|
100
|
+
};
|
|
101
|
+
Font._urls.set(fontFamily, url);
|
|
102
|
+
Font._factories.set(fontFamily, CreateGeometry);
|
|
103
|
+
return CreateGeometry;
|
|
104
|
+
} else throw new Error(`Font download failed (${String(response.status)}): ${url}`);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
//#endregion
|
|
108
|
+
export { Font as default };
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=font.js.map
|