znlgis 博客

GIS开发与技术分享

第六章:二次开发进阶

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二次开发的进阶主题,包括:

  1. 自定义渲染器:自定义材质、后处理效果、可视化对象
  2. 高级几何算法:形状分析、曲面重建、几何修复
  3. 性能优化:渲染优化(LOD、视锥剔除、实例化)、几何缓存、异步处理
  4. 插件系统:插件接口设计、插件管理器、示例插件实现

通过掌握这些高级技术,开发者可以创建功能丰富、性能优良的Chili3D扩展应用。在下一章中,我们将通过实战案例来综合运用所学知识。


下一章预告:第七章将通过多个实战案例,展示如何综合运用前面所学的知识来开发实际应用,包括参数化建模工具、BIM组件库、自动化设计工具等。