第六章:二次开发进阶
6.1 自定义渲染器
6.1.1 渲染管线概述
Chili3D的渲染基于Three.js,理解其渲染管线对于自定义渲染至关重要:
场景图(Scene Graph)
↓
几何体处理(Geometry Processing)
↓
材质着色(Material Shading)
↓
光照计算(Lighting)
↓
后处理(Post-processing)
↓
最终输出(Final Output)
6.1.2 自定义材质
创建自定义着色器材质:
// packages/chili-extension/src/materials/xrayMaterial.ts
import * as THREE from "three";
export class XRayMaterial extends THREE.ShaderMaterial {
constructor(options: XRayMaterialOptions = {}) {
super({
uniforms: {
color: { value: new THREE.Color(options.color || 0x00ff00) },
opacity: { value: options.opacity || 0.5 },
edgeColor: { value: new THREE.Color(options.edgeColor || 0xffffff) },
edgeWidth: { value: options.edgeWidth || 1.0 },
time: { value: 0 }
},
vertexShader: `
varying vec3 vNormal;
varying vec3 vViewPosition;
void main() {
vNormal = normalize(normalMatrix * normal);
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vViewPosition = -mvPosition.xyz;
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
uniform vec3 color;
uniform float opacity;
uniform vec3 edgeColor;
uniform float edgeWidth;
uniform float time;
varying vec3 vNormal;
varying vec3 vViewPosition;
void main() {
// 计算视角与法线的夹角
vec3 viewDir = normalize(vViewPosition);
float edgeFactor = abs(dot(viewDir, vNormal));
// 边缘检测
float edge = 1.0 - smoothstep(0.0, edgeWidth * 0.1, edgeFactor);
// 混合颜色
vec3 finalColor = mix(color, edgeColor, edge);
float finalOpacity = mix(opacity * 0.5, opacity, edge);
// 添加轻微的动画效果
finalOpacity *= 0.9 + 0.1 * sin(time * 2.0);
gl_FragColor = vec4(finalColor, finalOpacity);
}
`,
transparent: true,
side: THREE.DoubleSide,
depthWrite: false
});
}
update(deltaTime: number): void {
this.uniforms.time.value += deltaTime;
}
}
interface XRayMaterialOptions {
color?: number;
opacity?: number;
edgeColor?: number;
edgeWidth?: number;
}
6.1.3 自定义后处理效果
// packages/chili-extension/src/effects/outlineEffect.ts
import * as THREE from "three";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass";
export class CustomOutlineEffect {
private _composer: EffectComposer;
private _outlinePass: ShaderPass;
constructor(
private renderer: THREE.WebGLRenderer,
private scene: THREE.Scene,
private camera: THREE.Camera
) {
this._composer = new EffectComposer(renderer);
// 添加渲染通道
const renderPass = new RenderPass(scene, camera);
this._composer.addPass(renderPass);
// 添加轮廓通道
this._outlinePass = new ShaderPass(this.createOutlineShader());
this._composer.addPass(this._outlinePass);
}
private createOutlineShader(): THREE.ShaderMaterial {
return new THREE.ShaderMaterial({
uniforms: {
tDiffuse: { value: null },
resolution: {
value: new THREE.Vector2(
window.innerWidth,
window.innerHeight
)
},
outlineColor: { value: new THREE.Color(0xff0000) },
outlineWidth: { value: 2.0 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform vec2 resolution;
uniform vec3 outlineColor;
uniform float outlineWidth;
varying vec2 vUv;
void main() {
vec4 color = texture2D(tDiffuse, vUv);
// Sobel边缘检测
vec2 texel = vec2(1.0 / resolution.x, 1.0 / resolution.y);
float gx = 0.0;
float gy = 0.0;
// 采样周围像素
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
vec4 sample = texture2D(tDiffuse, vUv + vec2(float(i), float(j)) * texel * outlineWidth);
float gray = dot(sample.rgb, vec3(0.299, 0.587, 0.114));
// Sobel算子
gx += gray * float(i) * (abs(j) == 1 ? 1.0 : 2.0);
gy += gray * float(j) * (abs(i) == 1 ? 1.0 : 2.0);
}
}
float edge = sqrt(gx * gx + gy * gy);
// 混合轮廓
vec3 finalColor = mix(color.rgb, outlineColor, edge * 0.5);
gl_FragColor = vec4(finalColor, color.a);
}
`
});
}
render(): void {
this._composer.render();
}
setSize(width: number, height: number): void {
this._composer.setSize(width, height);
this._outlinePass.uniforms.resolution.value.set(width, height);
}
setOutlineColor(color: number): void {
this._outlinePass.uniforms.outlineColor.value.set(color);
}
setOutlineWidth(width: number): void {
this._outlinePass.uniforms.outlineWidth.value = width;
}
}
6.1.4 自定义可视化对象
// packages/chili-extension/src/visual/dimensionVisual.ts
import * as THREE from "three";
export class DimensionVisual extends THREE.Group {
private _startPoint: THREE.Vector3;
private _endPoint: THREE.Vector3;
private _textSprite: THREE.Sprite;
private _lines: THREE.LineSegments;
constructor(start: XYZ, end: XYZ, options: DimensionOptions = {}) {
super();
this._startPoint = new THREE.Vector3(start.x, start.y, start.z);
this._endPoint = new THREE.Vector3(end.x, end.y, end.z);
// 创建尺寸线
this._lines = this.createDimensionLines(options);
this.add(this._lines);
// 创建文字标签
this._textSprite = this.createTextSprite(options);
this.add(this._textSprite);
}
private createDimensionLines(options: DimensionOptions): THREE.LineSegments {
const offset = options.offset || 10;
const extensionLength = options.extensionLength || 5;
// 计算尺寸线方向
const direction = new THREE.Vector3().subVectors(this._endPoint, this._startPoint);
const length = direction.length();
direction.normalize();
// 计算偏移方向(垂直于尺寸线)
const offsetDir = new THREE.Vector3(0, 0, 1).cross(direction).normalize();
// 创建几何体
const geometry = new THREE.BufferGeometry();
const vertices: number[] = [];
// 起点延长线
const p1 = this._startPoint.clone();
const p2 = p1.clone().add(offsetDir.clone().multiplyScalar(offset + extensionLength));
vertices.push(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z);
// 终点延长线
const p3 = this._endPoint.clone();
const p4 = p3.clone().add(offsetDir.clone().multiplyScalar(offset + extensionLength));
vertices.push(p3.x, p3.y, p3.z, p4.x, p4.y, p4.z);
// 尺寸线
const p5 = this._startPoint.clone().add(offsetDir.clone().multiplyScalar(offset));
const p6 = this._endPoint.clone().add(offsetDir.clone().multiplyScalar(offset));
vertices.push(p5.x, p5.y, p5.z, p6.x, p6.y, p6.z);
// 箭头
const arrowSize = options.arrowSize || 3;
const arrowAngle = Math.PI / 6;
// 起点箭头
const arrow1 = direction.clone().multiplyScalar(arrowSize);
const arrow1a = arrow1.clone().applyAxisAngle(offsetDir, arrowAngle);
const arrow1b = arrow1.clone().applyAxisAngle(offsetDir, -arrowAngle);
vertices.push(p5.x, p5.y, p5.z, p5.x + arrow1a.x, p5.y + arrow1a.y, p5.z + arrow1a.z);
vertices.push(p5.x, p5.y, p5.z, p5.x + arrow1b.x, p5.y + arrow1b.y, p5.z + arrow1b.z);
// 终点箭头
const arrow2 = direction.clone().negate().multiplyScalar(arrowSize);
const arrow2a = arrow2.clone().applyAxisAngle(offsetDir, arrowAngle);
const arrow2b = arrow2.clone().applyAxisAngle(offsetDir, -arrowAngle);
vertices.push(p6.x, p6.y, p6.z, p6.x + arrow2a.x, p6.y + arrow2a.y, p6.z + arrow2a.z);
vertices.push(p6.x, p6.y, p6.z, p6.x + arrow2b.x, p6.y + arrow2b.y, p6.z + arrow2b.z);
geometry.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3));
const material = new THREE.LineBasicMaterial({
color: options.color || 0x000000,
linewidth: options.lineWidth || 1
});
return new THREE.LineSegments(geometry, material);
}
private createTextSprite(options: DimensionOptions): THREE.Sprite {
const distance = this._startPoint.distanceTo(this._endPoint);
const text = options.prefix
? `${options.prefix}${distance.toFixed(options.decimals || 2)}`
: distance.toFixed(options.decimals || 2);
// 创建canvas
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d")!;
const fontSize = options.fontSize || 16;
context.font = `${fontSize}px Arial`;
const textWidth = context.measureText(text).width;
canvas.width = textWidth + 10;
canvas.height = fontSize + 10;
context.font = `${fontSize}px Arial`;
context.fillStyle = options.textColor || "#000000";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText(text, canvas.width / 2, canvas.height / 2);
// 创建纹理
const texture = new THREE.CanvasTexture(canvas);
// 创建精灵
const material = new THREE.SpriteMaterial({ map: texture });
const sprite = new THREE.Sprite(material);
// 设置位置(尺寸线中点)
const midPoint = new THREE.Vector3()
.addVectors(this._startPoint, this._endPoint)
.multiplyScalar(0.5);
const offset = options.offset || 10;
const direction = new THREE.Vector3().subVectors(this._endPoint, this._startPoint);
const offsetDir = new THREE.Vector3(0, 0, 1).cross(direction).normalize();
midPoint.add(offsetDir.multiplyScalar(offset + 5));
sprite.position.copy(midPoint);
// 设置缩放
sprite.scale.set(canvas.width / 10, canvas.height / 10, 1);
return sprite;
}
update(start: XYZ, end: XYZ, options?: DimensionOptions): void {
this._startPoint.set(start.x, start.y, start.z);
this._endPoint.set(end.x, end.y, end.z);
// 重建可视化
this.remove(this._lines);
this.remove(this._textSprite);
this._lines = this.createDimensionLines(options || {});
this._textSprite = this.createTextSprite(options || {});
this.add(this._lines);
this.add(this._textSprite);
}
}
interface DimensionOptions {
offset?: number;
extensionLength?: number;
arrowSize?: number;
color?: number;
lineWidth?: number;
fontSize?: number;
textColor?: string;
prefix?: string;
decimals?: number;
}
6.2 高级几何算法
6.2.1 自定义形状分析
// packages/chili-extension/src/analysis/shapeAnalyzer.ts
export class ShapeAnalyzer {
/**
* 计算形状的质量属性
*/
static computeMassProperties(shape: IShape): MassProperties {
const handle = (shape as Shape).handle;
const result = wasm.computeMassProperties(handle);
return {
volume: result.volume,
area: result.area,
mass: result.mass,
centerOfGravity: new XYZ(
result.cogX,
result.cogY,
result.cogZ
),
momentOfInertia: {
ixx: result.ixx,
iyy: result.iyy,
izz: result.izz,
ixy: result.ixy,
ixz: result.ixz,
iyz: result.iyz
}
};
}
/**
* 检查形状有效性
*/
static validateShape(shape: IShape): ValidationResult {
const handle = (shape as Shape).handle;
const issues: ValidationIssue[] = [];
// 检查拓扑有效性
if (!wasm.isShapeValid(handle)) {
issues.push({
severity: "error",
message: "Shape topology is invalid"
});
}
// 检查几何有效性
const geomCheck = wasm.checkGeometry(handle);
if (!geomCheck.valid) {
issues.push({
severity: "error",
message: geomCheck.message
});
}
// 检查曲面连续性
const contCheck = wasm.checkContinuity(handle);
if (contCheck.gaps.length > 0) {
for (const gap of contCheck.gaps) {
issues.push({
severity: "warning",
message: `Gap found: ${gap.size.toFixed(6)}`,
location: new XYZ(gap.x, gap.y, gap.z)
});
}
}
return {
valid: issues.filter(i => i.severity === "error").length === 0,
issues
};
}
/**
* 计算形状之间的干涉
*/
static checkInterference(shape1: IShape, shape2: IShape): InterferenceResult {
const handle1 = (shape1 as Shape).handle;
const handle2 = (shape2 as Shape).handle;
const result = wasm.checkInterference(handle1, handle2);
return {
hasInterference: result.hasInterference,
interferenceVolume: result.volume,
interferenceShape: result.shapeHandle
? new Shape(result.shapeHandle)
: undefined
};
}
/**
* 计算最小包围盒
*/
static computeOrientedBoundingBox(shape: IShape): OrientedBoundingBox {
const handle = (shape as Shape).handle;
const result = wasm.computeOBB(handle);
return {
center: new XYZ(result.centerX, result.centerY, result.centerZ),
halfExtents: new XYZ(result.halfX, result.halfY, result.halfZ),
axes: [
new XYZ(result.axis1X, result.axis1Y, result.axis1Z),
new XYZ(result.axis2X, result.axis2Y, result.axis2Z),
new XYZ(result.axis3X, result.axis3Y, result.axis3Z)
]
};
}
}
interface MassProperties {
volume: number;
area: number;
mass: number;
centerOfGravity: XYZ;
momentOfInertia: {
ixx: number;
iyy: number;
izz: number;
ixy: number;
ixz: number;
iyz: number;
};
}
interface ValidationResult {
valid: boolean;
issues: ValidationIssue[];
}
interface ValidationIssue {
severity: "error" | "warning" | "info";
message: string;
location?: XYZ;
}
6.2.2 曲面重建
// packages/chili-extension/src/algorithms/surfaceReconstruction.ts
export class SurfaceReconstructor {
/**
* 从点云重建曲面
*/
static fromPointCloud(
points: XYZ[],
options: ReconstructionOptions = {}
): Result<IShape> {
if (points.length < 3) {
return Result.error("Need at least 3 points");
}
// 转换点数据
const pointArray = new Float64Array(points.length * 3);
for (let i = 0; i < points.length; i++) {
pointArray[i * 3] = points[i].x;
pointArray[i * 3 + 1] = points[i].y;
pointArray[i * 3 + 2] = points[i].z;
}
try {
const handle = wasm.reconstructSurface(
pointArray,
options.degree || 3,
options.tolerance || 0.1,
options.smoothing || 0.5
);
return Result.ok(new Shape(handle));
} catch (e) {
return Result.error(`Reconstruction failed: ${e}`);
}
}
/**
* 从截面曲线创建放样曲面
*/
static loftFromSections(
sections: IWire[],
options: LoftOptions = {}
): Result<IShape> {
if (sections.length < 2) {
return Result.error("Need at least 2 sections");
}
const handles = sections.map(s => (s as Shape).handle);
try {
const handle = wasm.makeLoft(
handles,
options.solid ?? true,
options.ruled ?? false,
options.closed ?? false
);
return Result.ok(new Shape(handle));
} catch (e) {
return Result.error(`Loft failed: ${e}`);
}
}
/**
* 曲面偏移
*/
static offsetSurface(
face: IFace,
distance: number,
options: OffsetOptions = {}
): Result<IShape> {
const handle = (face as Shape).handle;
try {
const resultHandle = wasm.offsetSurface(
handle,
distance,
options.tolerance || 1e-6,
options.mode || "pipe"
);
return Result.ok(new Shape(resultHandle));
} catch (e) {
return Result.error(`Offset failed: ${e}`);
}
}
/**
* 曲面填充
*/
static fillBetweenCurves(
curves: ICurve[],
options: FillOptions = {}
): Result<IShape> {
if (curves.length < 2) {
return Result.error("Need at least 2 curves");
}
const handles = curves.map(c => (c as Curve).handle);
try {
const resultHandle = wasm.fillSurface(
handles,
options.continuity || "G1",
options.degree || 3,
options.maxSegments || 10
);
return Result.ok(new Shape(resultHandle));
} catch (e) {
return Result.error(`Fill failed: ${e}`);
}
}
}
interface ReconstructionOptions {
degree?: number;
tolerance?: number;
smoothing?: number;
}
interface LoftOptions {
solid?: boolean;
ruled?: boolean;
closed?: boolean;
}
interface OffsetOptions {
tolerance?: number;
mode?: "skin" | "pipe" | "solid";
}
interface FillOptions {
continuity?: "G0" | "G1" | "G2";
degree?: number;
maxSegments?: number;
}
6.2.3 几何修复
// packages/chili-extension/src/algorithms/geometryHealing.ts
export class GeometryHealer {
/**
* 修复形状中的问题
*/
static heal(shape: IShape, options: HealOptions = {}): Result<IShape> {
const handle = (shape as Shape).handle;
try {
// 修复顺序:缝合 -> 修复边 -> 修复面 -> 合并
let currentHandle = handle;
// 1. 缝合开放边
if (options.sewFaces !== false) {
currentHandle = wasm.sewShape(
currentHandle,
options.sewTolerance || 1e-6
);
}
// 2. 修复边
if (options.fixEdges !== false) {
currentHandle = wasm.fixEdges(currentHandle);
}
// 3. 修复面
if (options.fixFaces !== false) {
currentHandle = wasm.fixFaces(currentHandle);
}
// 4. 合并共面面
if (options.unifyFaces !== false) {
currentHandle = wasm.unifyFaces(currentHandle);
}
return Result.ok(new Shape(currentHandle));
} catch (e) {
return Result.error(`Healing failed: ${e}`);
}
}
/**
* 移除小特征
*/
static removeSmallFeatures(
shape: IShape,
minSize: number
): Result<IShape> {
const handle = (shape as Shape).handle;
try {
const resultHandle = wasm.removeSmallFeatures(handle, minSize);
return Result.ok(new Shape(resultHandle));
} catch (e) {
return Result.error(`Remove small features failed: ${e}`);
}
}
/**
* 简化形状
*/
static simplify(
shape: IShape,
options: SimplifyOptions = {}
): Result<IShape> {
const handle = (shape as Shape).handle;
try {
const resultHandle = wasm.simplifyShape(
handle,
options.linearTolerance || 0.01,
options.angularTolerance || 0.1
);
return Result.ok(new Shape(resultHandle));
} catch (e) {
return Result.error(`Simplify failed: ${e}`);
}
}
}
interface HealOptions {
sewFaces?: boolean;
sewTolerance?: number;
fixEdges?: boolean;
fixFaces?: boolean;
unifyFaces?: boolean;
}
interface SimplifyOptions {
linearTolerance?: number;
angularTolerance?: number;
}
6.3 性能优化
6.3.1 渲染性能优化
// packages/chili-extension/src/optimization/renderOptimizer.ts
export class RenderOptimizer {
private _lodManager: LODManager;
private _frustumCuller: FrustumCuller;
private _instanceManager: InstanceManager;
constructor(private view: IView) {
this._lodManager = new LODManager();
this._frustumCuller = new FrustumCuller(view.camera);
this._instanceManager = new InstanceManager();
}
/**
* 优化场景渲染
*/
optimize(): void {
const objects = this.view.getAllObjects();
for (const obj of objects) {
// 视锥剔除
if (!this._frustumCuller.isVisible(obj)) {
obj.visible = false;
continue;
}
obj.visible = true;
// LOD选择
const distance = this.getDistanceToCamera(obj);
const lodLevel = this._lodManager.selectLevel(obj, distance);
this.applyLOD(obj, lodLevel);
}
// 实例化相似对象
this._instanceManager.updateInstances(objects);
}
private getDistanceToCamera(obj: THREE.Object3D): number {
const camera = this.view.camera;
return obj.position.distanceTo(camera.position);
}
private applyLOD(obj: THREE.Object3D, level: number): void {
if (obj instanceof THREE.Mesh && obj.userData.lodMeshes) {
const lodMeshes = obj.userData.lodMeshes as THREE.BufferGeometry[];
if (lodMeshes[level]) {
obj.geometry = lodMeshes[level];
}
}
}
}
/**
* LOD管理器
*/
class LODManager {
private _levels: number[] = [100, 500, 1000, 2000];
selectLevel(obj: THREE.Object3D, distance: number): number {
for (let i = 0; i < this._levels.length; i++) {
if (distance < this._levels[i]) {
return i;
}
}
return this._levels.length - 1;
}
/**
* 生成LOD几何体
*/
generateLODs(geometry: THREE.BufferGeometry): THREE.BufferGeometry[] {
const lods: THREE.BufferGeometry[] = [geometry];
// 使用简化算法生成不同级别
const simplifyRatios = [1, 0.5, 0.25, 0.1];
for (let i = 1; i < simplifyRatios.length; i++) {
const simplified = this.simplifyGeometry(geometry, simplifyRatios[i]);
lods.push(simplified);
}
return lods;
}
private simplifyGeometry(
geometry: THREE.BufferGeometry,
ratio: number
): THREE.BufferGeometry {
// 使用简化算法(例如quadric error metrics)
// 这里是简化实现
const simplified = geometry.clone();
// 实际应用中应使用专门的简化库
// 如SimplifyModifier
return simplified;
}
}
/**
* 视锥剔除器
*/
class FrustumCuller {
private _frustum: THREE.Frustum = new THREE.Frustum();
private _projScreenMatrix: THREE.Matrix4 = new THREE.Matrix4();
constructor(private camera: THREE.Camera) {}
update(): void {
this._projScreenMatrix.multiplyMatrices(
this.camera.projectionMatrix,
this.camera.matrixWorldInverse
);
this._frustum.setFromProjectionMatrix(this._projScreenMatrix);
}
isVisible(object: THREE.Object3D): boolean {
if (!object.geometry) return true;
if (!object.geometry.boundingSphere) {
object.geometry.computeBoundingSphere();
}
const sphere = object.geometry.boundingSphere!.clone();
sphere.applyMatrix4(object.matrixWorld);
return this._frustum.intersectsSphere(sphere);
}
}
/**
* 实例管理器
*/
class InstanceManager {
private _instanceGroups: Map<string, THREE.InstancedMesh> = new Map();
updateInstances(objects: THREE.Object3D[]): void {
// 按几何体和材质分组
const groups = this.groupByGeometryAndMaterial(objects);
for (const [key, group] of groups) {
if (group.length > 10) { // 超过10个相同对象时使用实例化
this.createInstancedMesh(key, group);
}
}
}
private groupByGeometryAndMaterial(
objects: THREE.Object3D[]
): Map<string, THREE.Mesh[]> {
const groups = new Map<string, THREE.Mesh[]>();
for (const obj of objects) {
if (!(obj instanceof THREE.Mesh)) continue;
const key = `${obj.geometry.uuid}_${(obj.material as THREE.Material).uuid}`;
if (!groups.has(key)) {
groups.set(key, []);
}
groups.get(key)!.push(obj);
}
return groups;
}
private createInstancedMesh(key: string, meshes: THREE.Mesh[]): void {
const template = meshes[0];
const instancedMesh = new THREE.InstancedMesh(
template.geometry,
template.material as THREE.Material,
meshes.length
);
for (let i = 0; i < meshes.length; i++) {
instancedMesh.setMatrixAt(i, meshes[i].matrixWorld);
}
instancedMesh.instanceMatrix.needsUpdate = true;
this._instanceGroups.set(key, instancedMesh);
}
}
6.3.2 几何计算优化
// packages/chili-extension/src/optimization/geometryCache.ts
export class GeometryCache {
private static _instance: GeometryCache;
private _cache: Map<string, CacheEntry> = new Map();
private _maxSize: number = 100 * 1024 * 1024; // 100MB
private _currentSize: number = 0;
static get instance(): GeometryCache {
if (!this._instance) {
this._instance = new GeometryCache();
}
return this._instance;
}
/**
* 获取或计算几何体
*/
getOrCompute<T>(
key: string,
computeFn: () => T,
sizeEstimate: number
): T {
// 检查缓存
if (this._cache.has(key)) {
const entry = this._cache.get(key)!;
entry.lastAccess = Date.now();
entry.accessCount++;
return entry.value as T;
}
// 计算新值
const value = computeFn();
// 确保有足够空间
this.ensureSpace(sizeEstimate);
// 添加到缓存
this._cache.set(key, {
value,
size: sizeEstimate,
lastAccess: Date.now(),
accessCount: 1
});
this._currentSize += sizeEstimate;
return value;
}
/**
* 使缓存项失效
*/
invalidate(key: string): void {
const entry = this._cache.get(key);
if (entry) {
this._currentSize -= entry.size;
this._cache.delete(key);
}
}
/**
* 清空缓存
*/
clear(): void {
this._cache.clear();
this._currentSize = 0;
}
private ensureSpace(requiredSize: number): void {
while (this._currentSize + requiredSize > this._maxSize && this._cache.size > 0) {
this.evictLeastRecentlyUsed();
}
}
private evictLeastRecentlyUsed(): void {
let lruKey: string | null = null;
let lruTime = Infinity;
for (const [key, entry] of this._cache) {
if (entry.lastAccess < lruTime) {
lruTime = entry.lastAccess;
lruKey = key;
}
}
if (lruKey) {
this.invalidate(lruKey);
}
}
}
interface CacheEntry {
value: any;
size: number;
lastAccess: number;
accessCount: number;
}
6.3.3 异步处理
// packages/chili-extension/src/optimization/asyncProcessor.ts
export class AsyncProcessor {
private _workers: Worker[] = [];
private _taskQueue: Task[] = [];
private _maxWorkers: number;
private _busyWorkers: Set<Worker> = new Set();
constructor(maxWorkers: number = navigator.hardwareConcurrency || 4) {
this._maxWorkers = maxWorkers;
this.initWorkers();
}
private initWorkers(): void {
for (let i = 0; i < this._maxWorkers; i++) {
const worker = new Worker(
new URL("./geometryWorker.ts", import.meta.url)
);
worker.onmessage = (e) => this.handleWorkerMessage(worker, e);
worker.onerror = (e) => this.handleWorkerError(worker, e);
this._workers.push(worker);
}
}
/**
* 提交任务
*/
submit<T>(task: TaskDefinition): Promise<T> {
return new Promise((resolve, reject) => {
this._taskQueue.push({
...task,
resolve,
reject
});
this.processQueue();
});
}
/**
* 批量处理
*/
async batchProcess<T, R>(
items: T[],
processFn: (item: T) => TaskDefinition
): Promise<R[]> {
const tasks = items.map(item => this.submit<R>(processFn(item)));
return Promise.all(tasks);
}
private processQueue(): void {
if (this._taskQueue.length === 0) return;
// 找到空闲的worker
const freeWorker = this._workers.find(w => !this._busyWorkers.has(w));
if (!freeWorker) return;
// 取出任务
const task = this._taskQueue.shift()!;
// 标记worker为忙碌
this._busyWorkers.add(freeWorker);
// 存储任务信息以便后续处理
(freeWorker as any).__currentTask = task;
// 发送任务到worker
freeWorker.postMessage({
type: task.type,
data: task.data
});
}
private handleWorkerMessage(worker: Worker, e: MessageEvent): void {
const task = (worker as any).__currentTask as Task;
delete (worker as any).__currentTask;
this._busyWorkers.delete(worker);
if (e.data.error) {
task.reject(new Error(e.data.error));
} else {
task.resolve(e.data.result);
}
// 继续处理队列
this.processQueue();
}
private handleWorkerError(worker: Worker, e: ErrorEvent): void {
const task = (worker as any).__currentTask as Task;
if (task) {
delete (worker as any).__currentTask;
this._busyWorkers.delete(worker);
task.reject(new Error(e.message));
}
this.processQueue();
}
dispose(): void {
for (const worker of this._workers) {
worker.terminate();
}
this._workers = [];
}
}
interface TaskDefinition {
type: string;
data: any;
}
interface Task extends TaskDefinition {
resolve: (value: any) => void;
reject: (error: Error) => void;
}
// geometryWorker.ts
self.onmessage = function(e) {
const { type, data } = e.data;
try {
let result;
switch (type) {
case "mesh":
result = meshShape(data.shape, data.deflection);
break;
case "boolean":
result = performBoolean(data.shape1, data.shape2, data.operation);
break;
case "intersect":
result = computeIntersections(data.shapes);
break;
default:
throw new Error(`Unknown task type: ${type}`);
}
self.postMessage({ result });
} catch (error) {
self.postMessage({ error: (error as Error).message });
}
};
6.4 插件系统
6.4.1 插件接口设计
// packages/chili-extension/src/plugin/pluginInterface.ts
export interface IPlugin {
readonly id: string;
readonly name: string;
readonly version: string;
readonly description?: string;
readonly author?: string;
// 生命周期
onLoad(app: IApplication): Promise<void>;
onUnload(): Promise<void>;
// 可选的扩展点
getCommands?(): ICommand[];
getRibbonConfig?(): RibbonConfig;
getPropertyEditors?(): PropertyEditorConfig[];
getToolbars?(): ToolbarConfig[];
}
export interface PluginManifest {
id: string;
name: string;
version: string;
description?: string;
author?: string;
main: string;
dependencies?: Record<string, string>;
permissions?: string[];
}
6.4.2 插件管理器
// packages/chili-extension/src/plugin/pluginManager.ts
export class PluginManager {
private _plugins: Map<string, PluginInstance> = new Map();
private _app: IApplication;
constructor(app: IApplication) {
this._app = app;
}
/**
* 加载插件
*/
async loadPlugin(url: string): Promise<void> {
// 加载插件清单
const manifestUrl = `${url}/manifest.json`;
const manifestResponse = await fetch(manifestUrl);
const manifest: PluginManifest = await manifestResponse.json();
// 检查依赖
await this.checkDependencies(manifest.dependencies);
// 加载插件模块
const moduleUrl = `${url}/${manifest.main}`;
const module = await import(moduleUrl);
// 创建插件实例
const Plugin = module.default as new () => IPlugin;
const plugin = new Plugin();
// 验证插件
this.validatePlugin(plugin, manifest);
// 注册插件
const instance: PluginInstance = {
manifest,
plugin,
loaded: false
};
this._plugins.set(manifest.id, instance);
// 加载插件
await plugin.onLoad(this._app);
instance.loaded = true;
// 注册扩展
this.registerExtensions(plugin);
}
/**
* 卸载插件
*/
async unloadPlugin(id: string): Promise<void> {
const instance = this._plugins.get(id);
if (!instance) return;
if (instance.loaded) {
await instance.plugin.onUnload();
}
// 注销扩展
this.unregisterExtensions(instance.plugin);
this._plugins.delete(id);
}
/**
* 获取已加载的插件
*/
getLoadedPlugins(): IPlugin[] {
return Array.from(this._plugins.values())
.filter(i => i.loaded)
.map(i => i.plugin);
}
private async checkDependencies(
dependencies?: Record<string, string>
): Promise<void> {
if (!dependencies) return;
for (const [id, version] of Object.entries(dependencies)) {
const plugin = this._plugins.get(id);
if (!plugin) {
throw new Error(`Missing dependency: ${id}`);
}
if (!this.isVersionCompatible(plugin.manifest.version, version)) {
throw new Error(
`Incompatible version for ${id}: ` +
`required ${version}, found ${plugin.manifest.version}`
);
}
}
}
private validatePlugin(plugin: IPlugin, manifest: PluginManifest): void {
if (plugin.id !== manifest.id) {
throw new Error(
`Plugin ID mismatch: ${plugin.id} vs ${manifest.id}`
);
}
if (plugin.version !== manifest.version) {
throw new Error(
`Plugin version mismatch: ${plugin.version} vs ${manifest.version}`
);
}
}
private registerExtensions(plugin: IPlugin): void {
// 注册命令
if (plugin.getCommands) {
for (const command of plugin.getCommands()) {
CommandRegistry.register(command);
}
}
// 注册功能区配置
if (plugin.getRibbonConfig) {
// 应用功能区配置
}
// 注册属性编辑器
if (plugin.getPropertyEditors) {
// 应用属性编辑器配置
}
}
private unregisterExtensions(plugin: IPlugin): void {
// 注销命令
if (plugin.getCommands) {
for (const command of plugin.getCommands()) {
CommandRegistry.unregister(command);
}
}
}
private isVersionCompatible(actual: string, required: string): boolean {
// 简单的版本比较逻辑
const actualParts = actual.split(".").map(Number);
const requiredParts = required.split(".").map(Number);
for (let i = 0; i < requiredParts.length; i++) {
if (actualParts[i] < requiredParts[i]) return false;
if (actualParts[i] > requiredParts[i]) return true;
}
return true;
}
}
interface PluginInstance {
manifest: PluginManifest;
plugin: IPlugin;
loaded: boolean;
}
6.4.3 示例插件
// example-plugin/src/index.ts
import { IPlugin, IApplication, command, ICommand, IDocument } from "chili-core";
export default class ExamplePlugin implements IPlugin {
readonly id = "example-plugin";
readonly name = "Example Plugin";
readonly version = "1.0.0";
readonly description = "An example plugin for Chili3D";
readonly author = "Developer";
private _app: IApplication | undefined;
async onLoad(app: IApplication): Promise<void> {
this._app = app;
console.log(`${this.name} loaded`);
// 初始化插件功能
this.initializeFeatures();
}
async onUnload(): Promise<void> {
console.log(`${this.name} unloaded`);
// 清理资源
this.cleanup();
}
getCommands(): ICommand[] {
return [new ExampleCommand()];
}
getRibbonConfig(): RibbonConfig {
return {
tabs: [
{
id: "example",
label: "ribbon.example",
groups: [
{
id: "tools",
label: "ribbon.tools",
buttons: [
{
command: "Example.Command",
icon: "icon-example",
label: "command.example"
}
]
}
]
}
]
};
}
private initializeFeatures(): void {
// 初始化插件特定功能
}
private cleanup(): void {
// 清理资源
}
}
@command({
name: "Example.Command",
icon: "icon-example",
display: "command.example"
})
class ExampleCommand implements ICommand {
async execute(document: IDocument): Promise<void> {
Toast.show("Example command executed!");
}
}
6.5 本章小结
本章深入探讨了Chili3D二次开发的进阶主题,包括:
- 自定义渲染器:自定义材质、后处理效果、可视化对象
- 高级几何算法:形状分析、曲面重建、几何修复
- 性能优化:渲染优化(LOD、视锥剔除、实例化)、几何缓存、异步处理
- 插件系统:插件接口设计、插件管理器、示例插件实现
通过掌握这些高级技术,开发者可以创建功能丰富、性能优良的Chili3D扩展应用。在下一章中,我们将通过实战案例来综合运用所学知识。
下一章预告:第七章将通过多个实战案例,展示如何综合运用前面所学的知识来开发实际应用,包括参数化建模工具、BIM组件库、自动化设计工具等。