未命名 JkZEFvedit icon

创建者:
用户tnhBzOUO
Fork(复制)
下载
嵌入
BUG反馈
index.html
index.html
            
            <!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CPU Transform - 150万线段示例</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            font-family: Arial, sans-serif;
            background: #000;
        }

        canvas {
            display: block;
            cursor: crosshair;
        }

        canvas.dragging {
            cursor: grabbing !important;
        }

        canvas.rotating {
            cursor: grab !important;
        }

        .info {
            position: absolute;
            top: 10px;
            left: 10px;
            color: white;
            background: rgba(0, 0, 0, 0.8);
            padding: 15px;
            border-radius: 8px;
            z-index: 100;
            font-size: 14px;
            line-height: 1.4;
        }

        .controls {
            position: absolute;
            top: 10px;
            right: 10px;
            z-index: 100;
        }

        .cpu-mode {
            color: #FFD700;
            font-weight: bold;
            text-shadow: 0 0 5px rgba(255, 215, 0, 0.5);
        }
        
        .mode-button {
            background: linear-gradient(45deg, #4CAF50, #2196F3);
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 8px;
            cursor: pointer;
            font-size: 16px;
            font-weight: bold;
            transition: all 0.3s ease;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
        }

        .mode-button:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 12px rgba(0, 0, 0, 0.4);
        }

        .mode-button.cpu-mode {
            background: linear-gradient(45deg, #FF5722, #FF9800);
        }

        .mode-button.gpu-mode {
            background: linear-gradient(45deg, #4CAF50, #2196F3);
        }

        .selection-box {
            position: absolute;
            border: 2px dashed #fff;
            background: rgba(255, 255, 255, 0.1);
            pointer-events: none;
            display: none;
        }

        .performance-indicator {
            position: absolute;
            bottom: 10px;
            left: 10px;
            color: white;
            padding: 10px;
            border-radius: 5px;
            z-index: 100;
            font-family: monospace;
            border: 2px solid #FF7A00;
        }

        .cpu-indicator {
            color: white;
            font-weight: bold;
        }
    </style>
</head>

<body>
    
    <div class="info">
        <div style="margin-top: 8px; padding-top: 8px; ">
            <div><strong>🖱️ 操作说明:</strong></div>
            <div style="margin-left: 10px; margin-top: 4px;">
                <div>• <kbd>Shift+左键拖拽</kbd> - 框选线段</div>
                <div>• <kbd>左键拖拽</kbd> - 拖动选中线段 / 旋转相机</div>
                <div>• <kbd>滚轮</kbd> - 缩放视角</div>
            </div>
        </div>
        <div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #444; font-size: 12px;">
            <div id="stats">线段数量: 0</div>
            <div id="transform-info">变换组数: 0</div>
            <div id="selected-info">已选中: 0 组</div>
        </div>
    </div>

    <div class="performance-indicator">
        <div id="fps">FPS: 0</div>
        <div id="transform-time" class="cpu-indicator">CPU变换耗时: 0ms</div>
    </div>

    <div class="controls">
        <button id="selectAll" class="mode-button cpu-mode">
            全部选中
        </button>
    </div>

    <div class="selection-box" id="selectionBox"></div>

    <script src="https://unpkg.com/[email protected]/build/three.min.js"></script>
    <script>
        // ========================
        // 🔧 CPU模式配置
        // ========================
        const CONFIG = {
            TOTAL_LINES: 1500000,       // 总线段数:150万条 (可手动修改)
            LINES_PER_GROUP: 100,       // 每组线段数:100条

            get MAX_TRANSFORMS() {
                return Math.ceil(this.TOTAL_LINES / this.LINES_PER_GROUP);
            },

            get GRID_SIZE() {
                const totalGroups = this.MAX_TRANSFORMS;
                const cubeRoot = Math.ceil(Math.pow(totalGroups, 1 / 3));
                let x = cubeRoot;
                let y = cubeRoot;
                let z = Math.ceil(totalGroups / (x * y));

                while (x * y * z < totalGroups) {
                    if (x <= y) x++;
                    else y++;
                    z = Math.ceil(totalGroups / (x * y));
                }

                return { X: x, Y: y, Z: z };
            },

            LINE_LENGTH_MIN: 3,
            LINE_LENGTH_MAX: 12,
            GROUP_SPREAD: 30,
            GROUP_SPACING: 40,

            INITIAL_CAMERA_DISTANCE: 1200,
            ZOOM_SPEED: 0.1,
            ROTATE_SPEED: 0.01,
            PAN_SPEED: 1.5,

            RAYCASTER_THRESHOLD: 15,
            DRAG_SENSITIVITY: 0.002,
        };

        class CPULineRenderer {
            constructor() {
                this.scene = new THREE.Scene();
                this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 20000);
                this.renderer = new THREE.WebGLRenderer({ antialias: false });
                this.raycaster = new THREE.Raycaster();

                // CPU模式:所有变换数据在CPU维护
                this.maxTransforms = CONFIG.MAX_TRANSFORMS;
                this.transforms = new Array(this.maxTransforms).fill(null).map(() => new THREE.Matrix4());
                this.originalPositions = null; // 存储原始本地坐标
                this.selectedTransforms = new Set();

                // 交互状态
                this.isSelecting = false;
                this.isDragging = false;
                this.isRotating = false;
                this.selectionStart = new THREE.Vector2();
                this.selectionEnd = new THREE.Vector2();
                this.lastMousePos = new THREE.Vector2();
                this.mouse = new THREE.Vector2();

                // 相机控制
                this.orbitTarget = new THREE.Vector3(0, 0, 0);
                this.orbitRadius = CONFIG.INITIAL_CAMERA_DISTANCE;
                this.orbitTheta = 0;
                this.orbitPhi = Math.PI / 4;

                // 线段对象
                this.lineMesh = null;

                // 性能监控
                this.transformStartTime = 0;
                this.cpuLoadHistory = [];

                this.init();

                window.lineRenderer = this;
            }

            init() {
                this.renderer.setSize(window.innerWidth, window.innerHeight);
                this.renderer.setClearColor(0x1a1a1a);
                document.body.appendChild(this.renderer.domElement);

                this.camera.position.set(300, 300, CONFIG.INITIAL_CAMERA_DISTANCE);
                this.camera.lookAt(0, 0, 0);

                this.setupCameraControls();

                const ambientLight = new THREE.AmbientLight(0x404040, 0.8);
                this.scene.add(ambientLight);

                this.raycaster.params.Line.threshold = CONFIG.RAYCASTER_THRESHOLD;

                this.createLines();
                this.setupEventListeners();
                this.updateCameraPosition();
                this.animate();
            }

            updateSelectedInfo() {
                const selectedInfo = document.getElementById('selected-info');
                const selectedCount = this.selectedTransforms.size;
                const affectedLines = selectedCount * CONFIG.LINES_PER_GROUP;
                selectedInfo.textContent = `已选中: ${selectedCount.toLocaleString()} 组 (${affectedLines.toLocaleString()} 条线段)`;
            }

            checkSelectedLinesHit(mouseX, mouseY) {
                if (this.selectedTransforms.size === 0) return false;

                this.mouse.x = (mouseX / window.innerWidth) * 2 - 1;
                this.mouse.y = -(mouseY / window.innerHeight) * 2 + 1;

                this.raycaster.setFromCamera(this.mouse, this.camera);

                const geometry = this.lineMesh.geometry;
                const positions = geometry.attributes.position.array;
                const groupIds = geometry.attributes.groupId.array;
                const selected = geometry.attributes.selectedFlag.array;

                const lineCount = positions.length / 6;
                const sampleStep = Math.max(1, Math.floor(lineCount / 5000));

                for (let i = 0; i < lineCount; i += sampleStep) {
                    if (selected[i * 2] > 0) {
                        const startIdx = i * 6;

                        // 直接使用当前的世界坐标
                        const worldStart = new THREE.Vector3(
                            positions[startIdx],
                            positions[startIdx + 1],
                            positions[startIdx + 2]
                        );
                        const worldEnd = new THREE.Vector3(
                            positions[startIdx + 3],
                            positions[startIdx + 4],
                            positions[startIdx + 5]
                        );

                        const lineDirection = worldEnd.clone().sub(worldStart).normalize();
                        const lineLength = worldStart.distanceTo(worldEnd);

                        const rayToLineStart = worldStart.clone().sub(this.raycaster.ray.origin);
                        const projection = rayToLineStart.dot(this.raycaster.ray.direction);
                        const closestPointOnRay = this.raycaster.ray.origin.clone()
                            .add(this.raycaster.ray.direction.clone().multiplyScalar(projection));

                        const lineToRayStart = this.raycaster.ray.origin.clone().sub(worldStart);
                        const t = Math.max(0, Math.min(lineLength, lineToRayStart.dot(lineDirection)));
                        const closestPointOnLine = worldStart.clone().add(lineDirection.clone().multiplyScalar(t));

                        const distance = closestPointOnRay.distanceTo(closestPointOnLine);

                        if (distance < CONFIG.RAYCASTER_THRESHOLD) {
                            return true;
                        }
                    }
                }

                return false;
            }

            createLines() {
                const geometry = new THREE.BufferGeometry();
                const positions = new Float32Array(CONFIG.TOTAL_LINES * 6);
                const colors = new Float32Array(CONFIG.TOTAL_LINES * 6);
                const groupIds = new Float32Array(CONFIG.TOTAL_LINES * 2);
                const selectionFlags = new Float32Array(CONFIG.TOTAL_LINES * 2);

                // 保存原始本地坐标,用于CPU变换计算
                this.originalPositions = new Float32Array(CONFIG.TOTAL_LINES * 6);

                let lineIndex = 0;

                for (let groupIndex = 0; groupIndex < CONFIG.MAX_TRANSFORMS; groupIndex++) {
                    const actualLinesInGroup = Math.min(CONFIG.LINES_PER_GROUP, CONFIG.TOTAL_LINES - lineIndex);
                    if (actualLinesInGroup <= 0) break;

                    for (let i = 0; i < actualLinesInGroup; i++) {
                        const localStartX = (Math.random() - 0.5) * CONFIG.GROUP_SPREAD;
                        const localStartY = (Math.random() - 0.5) * CONFIG.GROUP_SPREAD;
                        const localStartZ = (Math.random() - 0.5) * CONFIG.GROUP_SPREAD;

                        const length = CONFIG.LINE_LENGTH_MIN + Math.random() * (CONFIG.LINE_LENGTH_MAX - CONFIG.LINE_LENGTH_MIN);
                        const direction = new THREE.Vector3(
                            Math.random() - 0.5,
                            Math.random() - 0.5,
                            Math.random() - 0.5
                        ).normalize();

                        const localEndX = localStartX + direction.x * length;
                        const localEndY = localStartY + direction.y * length;
                        const localEndZ = localStartZ + direction.z * length;

                        const vertexIndex = lineIndex * 6;

                        // 保存本地坐标
                        this.originalPositions[vertexIndex] = localStartX;
                        this.originalPositions[vertexIndex + 1] = localStartY;
                        this.originalPositions[vertexIndex + 2] = localStartZ;
                        this.originalPositions[vertexIndex + 3] = localEndX;
                        this.originalPositions[vertexIndex + 4] = localEndY;
                        this.originalPositions[vertexIndex + 5] = localEndZ;

                        // 初始世界坐标(稍后通过CPU计算更新)
                        positions[vertexIndex] = localStartX;
                        positions[vertexIndex + 1] = localStartY;
                        positions[vertexIndex + 2] = localStartZ;
                        positions[vertexIndex + 3] = localEndX;
                        positions[vertexIndex + 4] = localEndY;
                        positions[vertexIndex + 5] = localEndZ;

                        // 基于组生成颜色
                        const colorSeed = groupIndex * 137;
                        const r = ((colorSeed % 256) / 255) * 0.8 + 0.2;
                        const g = (((colorSeed * 17) % 256) / 255) * 0.8 + 0.2;
                        const b = (((colorSeed * 31) % 256) / 255) * 0.8 + 0.2;

                        colors[vertexIndex] = r;
                        colors[vertexIndex + 1] = g;
                        colors[vertexIndex + 2] = b;
                        colors[vertexIndex + 3] = r;
                        colors[vertexIndex + 4] = g;
                        colors[vertexIndex + 5] = b;

                        groupIds[lineIndex * 2] = groupIndex;
                        groupIds[lineIndex * 2 + 1] = groupIndex;

                        selectionFlags[lineIndex * 2] = 0.0;
                        selectionFlags[lineIndex * 2 + 1] = 0.0;

                        lineIndex++;
                    }
                }

                // 初始化变换矩阵
                const gridSize = CONFIG.GRID_SIZE;
                for (let i = 0; i < CONFIG.MAX_TRANSFORMS; i++) {
                    const x = (i % gridSize.X - gridSize.X / 2) * CONFIG.GROUP_SPACING;
                    const y = (Math.floor(i / gridSize.X) % gridSize.Y - gridSize.Y / 2) * CONFIG.GROUP_SPACING;
                    const z = (Math.floor(i / (gridSize.X * gridSize.Y)) - gridSize.Z / 2) * CONFIG.GROUP_SPACING;

                    this.transforms[i].makeTranslation(x, y, z);
                }

                geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
                geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
                geometry.setAttribute('groupId', new THREE.BufferAttribute(groupIds, 1));
                geometry.setAttribute('selectedFlag', new THREE.BufferAttribute(selectionFlags, 1));

                // CPU模式:使用简单的材质,不需要复杂shader
                const material = new THREE.ShaderMaterial({
                    vertexShader: `
                        attribute vec3 color;
                        attribute float selectedFlag;
                        
                        varying vec3 vColor;
                        
                        void main() {
                            vColor = mix(color, vec3(1.0, 1.0, 0.0), selectedFlag * 0.8);
                            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                        }
                    `,
                    fragmentShader: `
                        varying vec3 vColor;
                        
                        void main() {
                            gl_FragColor = vec4(vColor, 1.0);
                        }
                    `
                });

                this.lineMesh = new THREE.LineSegments(geometry, material);
                this.scene.add(this.lineMesh);

                // CPU模式必须初始计算所有顶点的世界坐标
                this.updateAllPositions();
                
                document.getElementById('stats').textContent = `线段数量: ${CONFIG.TOTAL_LINES.toLocaleString()}`;
                document.getElementById('transform-info').textContent = `变换组数: ${CONFIG.MAX_TRANSFORMS.toLocaleString()}`;
                
                this.updateSelectedInfo();
            }

            // CPU模式:重新计算所有顶点的世界坐标
            updateAllPositions() {
                const startTime = performance.now();

                const geometry = this.lineMesh.geometry;
                const positions = geometry.attributes.position.array;
                const groupIds = geometry.attributes.groupId.array;

                const lineCount = this.originalPositions.length / 6;

                // CPU密集计算:每条线段都要进行矩阵变换
                for (let i = 0; i < lineCount; i++) {
                    const groupId = Math.floor(groupIds[i * 2]);
                    const transform = this.transforms[groupId];

                    const startIdx = i * 6;

                    // 变换起点
                    const localStart = new THREE.Vector3(
                        this.originalPositions[startIdx],
                        this.originalPositions[startIdx + 1],
                        this.originalPositions[startIdx + 2]
                    );
                    const worldStart = localStart.applyMatrix4(transform);
                    positions[startIdx] = worldStart.x;
                    positions[startIdx + 1] = worldStart.y;
                    positions[startIdx + 2] = worldStart.z;

                    // 变换终点
                    const localEnd = new THREE.Vector3(
                        this.originalPositions[startIdx + 3],
                        this.originalPositions[startIdx + 4],
                        this.originalPositions[startIdx + 5]
                    );
                    const worldEnd = localEnd.applyMatrix4(transform);
                    positions[startIdx + 3] = worldEnd.x;
                    positions[startIdx + 4] = worldEnd.y;
                    positions[startIdx + 5] = worldEnd.z;
                }

                // 通知GPU更新顶点数据
                geometry.attributes.position.needsUpdate = true;

                const endTime = performance.now();
                const transformTime = endTime - startTime;

                // CPU负载监控
                this.cpuLoadHistory.push(transformTime);
                if (this.cpuLoadHistory.length > 10) {
                    this.cpuLoadHistory.shift();
                }

                document.getElementById('transform-time').textContent = `CPU变换耗时: ${transformTime.toFixed(2)}ms`;

                // 计算CPU负载指标
                const avgLoad = this.cpuLoadHistory.reduce((a, b) => a + b, 0) / this.cpuLoadHistory.length;
                const linesPerMs = CONFIG.TOTAL_LINES / avgLoad;

                return transformTime;
            }

            setupCameraControls() {
                let isPanning = false;
                let lastX = 0, lastY = 0;

                this.renderer.domElement.addEventListener('wheel', (event) => {
                    event.preventDefault();

                    if (event.shiftKey) {
                        const panSpeed = CONFIG.PAN_SPEED * 2.0;
                        const right = new THREE.Vector3();
                        const up = new THREE.Vector3(0, 1, 0);

                        right.crossVectors(this.camera.getWorldDirection(new THREE.Vector3()), up).normalize();
                        up.crossVectors(right, this.camera.getWorldDirection(new THREE.Vector3())).normalize();

                        const deltaX = event.deltaX * panSpeed * 0.01;
                        const deltaY = event.deltaY * panSpeed * 0.01;

                        const panVector = right.clone().multiplyScalar(-deltaX).add(up.clone().multiplyScalar(deltaY));

                        this.orbitTarget.add(panVector);
                        this.camera.position.add(panVector);

                    } else {
                        const delta = event.deltaY * CONFIG.ZOOM_SPEED;

                        this.orbitRadius += delta;
                        this.orbitRadius = Math.max(100, Math.min(10000, this.orbitRadius));

                        this.updateCameraPosition();
                    }
                });

                this.renderer.domElement.addEventListener('mousedown', (event) => {
                    if (event.button === 1) {
                        isPanning = true;
                        lastX = event.clientX;
                        lastY = event.clientY;
                        event.preventDefault();
                        this.renderer.domElement.style.cursor = 'grabbing';
                    }
                });

                this.renderer.domElement.addEventListener('mousemove', (event) => {
                    if (isPanning) {
                        const deltaX = event.clientX - lastX;
                        const deltaY = event.clientY - lastY;

                        const right = new THREE.Vector3();
                        const up = new THREE.Vector3(0, 1, 0);

                        right.crossVectors(this.camera.getWorldDirection(new THREE.Vector3()), up).normalize();
                        up.crossVectors(right, this.camera.getWorldDirection(new THREE.Vector3())).normalize();

                        const panVector = right.clone().multiplyScalar(-deltaX * CONFIG.PAN_SPEED)
                            .add(up.clone().multiplyScalar(deltaY * CONFIG.PAN_SPEED));

                        this.orbitTarget.add(panVector);
                        this.updateCameraPosition();

                        lastX = event.clientX;
                        lastY = event.clientY;
                    }
                });

                this.renderer.domElement.addEventListener('mouseup', (event) => {
                    if (event.button === 1) {
                        isPanning = false;
                        this.renderer.domElement.style.cursor = 'crosshair';
                    }
                });

                this.renderer.domElement.addEventListener('contextmenu', (e) => e.preventDefault());
            }

            updateCameraPosition() {
                const x = this.orbitTarget.x + this.orbitRadius * Math.sin(this.orbitPhi) * Math.cos(this.orbitTheta);
                const y = this.orbitTarget.y + this.orbitRadius * Math.cos(this.orbitPhi);
                const z = this.orbitTarget.z + this.orbitRadius * Math.sin(this.orbitPhi) * Math.sin(this.orbitTheta);

                this.camera.position.set(x, y, z);
                this.camera.lookAt(this.orbitTarget);
            }

            setupEventListeners() {
                const canvas = this.renderer.domElement;
                const selectionBox = document.getElementById('selectionBox');

                document.getElementById('selectAll').addEventListener('click', () => {
                    this.selectAll();
                });

                canvas.addEventListener('mousedown', (event) => {
                    if (event.button === 0) {
                        if (event.shiftKey) {
                            this.isSelecting = true;
                            this.selectionStart.set(event.clientX, event.clientY);
                            this.selectionEnd.copy(this.selectionStart);
                            selectionBox.style.display = 'block';
                            canvas.style.cursor = 'crosshair';
                        } else {
                            if (this.checkSelectedLinesHit(event.clientX, event.clientY)) {
                                this.isDragging = true;
                                this.lastMousePos.set(event.clientX, event.clientY);
                                canvas.classList.add('dragging');
                            } else {
                                this.isRotating = true;
                                this.lastMousePos.set(event.clientX, event.clientY);
                                canvas.classList.add('rotating');
                            }
                        }
                        event.preventDefault();
                    }
                });

                canvas.addEventListener('mousemove', (event) => {
                    if (this.isSelecting) {
                        this.selectionEnd.set(event.clientX, event.clientY);
                        this.updateSelectionBox(selectionBox);
                    } else if (this.isDragging) {
                        const deltaX = event.clientX - this.lastMousePos.x;
                        const deltaY = event.clientY - this.lastMousePos.y;
                        this.moveSelectedTransforms(-deltaX, -deltaY);
                        this.lastMousePos.set(event.clientX, event.clientY);
                    } else if (this.isRotating) {
                        let deltaX = event.clientX - this.lastMousePos.x;
                        let deltaY = event.clientY - this.lastMousePos.y;

                        deltaX = -deltaX;
                        deltaY = -deltaY;

                        this.orbitTheta -= deltaX * CONFIG.ROTATE_SPEED;
                        this.orbitPhi += deltaY * CONFIG.ROTATE_SPEED;
                        this.orbitPhi = Math.max(0.01, Math.min(Math.PI - 0.01, this.orbitPhi));

                        this.updateCameraPosition();

                        this.lastMousePos.set(event.clientX, event.clientY);
                    }
                });

                canvas.addEventListener('mouseup', (event) => {
                    if (event.button === 0) {
                        if (this.isSelecting) {
                            this.performSelection();
                            this.isSelecting = false;
                            selectionBox.style.display = 'none';
                            canvas.style.cursor = 'crosshair';
                        } else if (this.isDragging) {
                            this.isDragging = false;
                            canvas.classList.remove('dragging');
                        } else if (this.isRotating) {
                            this.isRotating = false;
                            canvas.classList.remove('rotating');
                        }
                    }
                });

                canvas.addEventListener('mousemove', (event) => {
                    if (!this.isSelecting && !this.isDragging && !this.isRotating) {
                        if (this.selectedTransforms.size > 0 && this.checkSelectedLinesHit(event.clientX, event.clientY)) {
                            canvas.style.cursor = 'grab';
                        } else {
                            canvas.style.cursor = 'crosshair';
                        }
                    }
                });

                window.addEventListener('resize', () => this.onWindowResize());
            }

            updateSelectionBox(selectionBox) {
                const left = Math.min(this.selectionStart.x, this.selectionEnd.x);
                const top = Math.min(this.selectionStart.y, this.selectionEnd.y);
                const width = Math.abs(this.selectionEnd.x - this.selectionStart.x);
                const height = Math.abs(this.selectionEnd.y - this.selectionStart.y);

                selectionBox.style.left = left + 'px';
                selectionBox.style.top = top + 'px';
                selectionBox.style.width = width + 'px';
                selectionBox.style.height = height + 'px';
            }

            performSelection() {
                this.clearSelection();

                const left = Math.min(this.selectionStart.x, this.selectionEnd.x);
                const right = Math.max(this.selectionStart.x, this.selectionEnd.x);
                const top = Math.min(this.selectionStart.y, this.selectionEnd.y);
                const bottom = Math.max(this.selectionStart.y, this.selectionEnd.y);

                if (right - left < 5 || bottom - top < 5) return;

                const leftNDC = (left / window.innerWidth) * 2 - 1;
                const rightNDC = (right / window.innerWidth) * 2 - 1;
                const topNDC = -((top / window.innerHeight) * 2 - 1);
                const bottomNDC = -((bottom / window.innerHeight) * 2 - 1);

                const geometry = this.lineMesh.geometry;
                const positions = geometry.attributes.position.array;
                const groupIds = geometry.attributes.groupId.array;
                const selected = geometry.attributes.selectedFlag.array;

                const lineCount = positions.length / 6;
                const sampleStep = Math.max(1, Math.floor(lineCount / 50000));

                // CPU模式:直接使用当前的世界坐标进行检测
                for (let i = 0; i < lineCount; i += sampleStep) {
                    const groupId = Math.floor(groupIds[i * 2]);

                    const startIdx = i * 6;
                    const worldMidX = (positions[startIdx] + positions[startIdx + 3]) / 2;
                    const worldMidY = (positions[startIdx + 1] + positions[startIdx + 4]) / 2;
                    const worldMidZ = (positions[startIdx + 2] + positions[startIdx + 5]) / 2;

                    const worldPos = new THREE.Vector3(worldMidX, worldMidY, worldMidZ);
                    const screenPos = worldPos.project(this.camera);

                    if (screenPos.x >= leftNDC && screenPos.x <= rightNDC &&
                        screenPos.y >= bottomNDC && screenPos.y <= topNDC &&
                        screenPos.z >= -1 && screenPos.z <= 1) {

                        this.selectedTransforms.add(groupId);
                    }
                }

                // 更新所有选中组的线段标志
                for (let i = 0; i < selected.length / 2; i++) {
                    const groupId = Math.floor(groupIds[i * 2]);
                    if (this.selectedTransforms.has(groupId)) {
                        selected[i * 2] = 1.0;
                        selected[i * 2 + 1] = 1.0;
                    }
                }

                geometry.attributes.selectedFlag.needsUpdate = true;
                this.updateSelectedInfo();
            }

            // Ctrl+A 全选
            selectAll() {
                this.clearSelection();

                // 添加所有组到选中集合
                for (let i = 0; i < CONFIG.MAX_TRANSFORMS; i++) {
                    this.selectedTransforms.add(i);
                }

                const geometry = this.lineMesh.geometry;
                const selected = geometry.attributes.selectedFlag.array;
                selected.fill(1.0);
                geometry.attributes.selectedFlag.needsUpdate = true;

                this.updateSelectedInfo();

                const totalLines = this.selectedTransforms.size * CONFIG.LINES_PER_GROUP;
            }

            clearSelection() {
                this.selectedTransforms.clear();

                const selected = this.lineMesh.geometry.attributes.selectedFlag.array;
                selected.fill(0.0);
                this.lineMesh.geometry.attributes.selectedFlag.needsUpdate = true;

                this.updateSelectedInfo();
            }

            // CPU模式:移动选中的变换组
            moveSelectedTransforms(deltaX, deltaY) {
                if (this.selectedTransforms.size === 0) return;

                const distance = this.camera.position.distanceTo(this.orbitTarget);
                const moveFactor = distance * CONFIG.DRAG_SENSITIVITY;

                const cameraDirection = new THREE.Vector3();
                this.camera.getWorldDirection(cameraDirection);

                const right = new THREE.Vector3();
                const up = new THREE.Vector3(0, 1, 0);
                right.crossVectors(cameraDirection, up).normalize();
                up.crossVectors(right, cameraDirection).normalize();

                const moveVector = right.clone().multiplyScalar(-deltaX * moveFactor)
                    .add(up.clone().multiplyScalar(deltaY * moveFactor));

                // 更新变换矩阵
                this.selectedTransforms.forEach(transformId => {
                    const currentTransform = this.transforms[transformId];
                    const translateMatrix = new THREE.Matrix4().makeTranslation(
                        moveVector.x, moveVector.y, moveVector.z
                    );
                    this.transforms[transformId].multiplyMatrices(translateMatrix, currentTransform);
                });

                // CPU模式:重新计算所有顶点位置 - 这是性能瓶颈
                this.updateAllPositions();
            }

            onWindowResize() {
                this.camera.aspect = window.innerWidth / window.innerHeight;
                this.camera.updateProjectionMatrix();
                this.renderer.setSize(window.innerWidth, window.innerHeight);
            }

            animate() {
                requestAnimationFrame(() => this.animate());
                this.renderer.render(this.scene, this.camera);
            }
        }

        // CPU性能监控
        class CPUPerformanceMonitor {
            constructor() {
                this.lastTime = performance.now();
                this.frames = 0;
                this.fps = 0;
                this.frameTimeHistory = [];
                this.update();
            }

            update() {
                this.frames++;
                const now = performance.now();
                const frameTime = now - this.lastTime;

                this.frameTimeHistory.push(frameTime);
                if (this.frameTimeHistory.length > 60) {
                    this.frameTimeHistory.shift();
                }

                if (now >= this.lastTime + 1000) {
                    this.fps = Math.round((this.frames * 1000) / (now - this.lastTime));
                    this.frames = 0;
                    this.lastTime = now;

                    const fpsElement = document.getElementById('fps');
                    if (fpsElement) {
                        const avgFrameTime = this.frameTimeHistory.reduce((a, b) => a + b, 0) / this.frameTimeHistory.length;
                        fpsElement.textContent = `FPS: ${this.fps}`;
                    }
                }

                requestAnimationFrame(() => this.update());
            }
        }

        // 启动CPU模式应用
        document.addEventListener('DOMContentLoaded', () => {
            try {
                const app = new CPULineRenderer();
                const monitor = new CPUPerformanceMonitor();
            } catch (error) {
                console.error('启动失败:', error);
            }
        });

        // 键盘快捷键
        document.addEventListener('keydown', (event) => {
            if (!window.lineRenderer) return;

            switch (event.key.toLowerCase()) {
                case 'escape':
                    window.lineRenderer.clearSelection();
                    console.log('已清除选择');
                    break;

                case 'r':
                    window.lineRenderer.orbitTarget.set(0, 0, 0);
                    window.lineRenderer.orbitRadius = CONFIG.INITIAL_CAMERA_DISTANCE;
                    window.lineRenderer.orbitTheta = 0;
                    window.lineRenderer.orbitPhi = Math.PI / 4;
                    window.lineRenderer.updateCameraPosition();
                    console.log('相机已重置');
                    break;

                case 'a':
                    if (event.ctrlKey) {
                        window.lineRenderer.selectAll();
                        event.preventDefault();
                    }
                    break;
            }
        });

        // 配置修改工具
        window.updateConfig = function (newConfig) {
            Object.assign(CONFIG, newConfig);
            console.log('🔧 CPU模式配置已更新, 重新加载以生效');
            if (confirm('是否重新加载?')) {
                location.reload();
            }
        };

        window.CONFIG = CONFIG;

        // CPU模式特有的工具函数
        window.cpuUtils = {
            // 获取CPU负载统计
            getCPUStats: function () {
                if (!window.lineRenderer) return null;

                const renderer = window.lineRenderer;
                const selectedCount = renderer.selectedTransforms.size;
                const affectedLines = selectedCount * CONFIG.LINES_PER_GROUP;
                const avgCPUTime = renderer.cpuLoadHistory.length > 0
                    ? renderer.cpuLoadHistory.reduce((a, b) => a + b, 0) / renderer.cpuLoadHistory.length
                    : 0;

                return {
                    selectedGroups: selectedCount,
                    affectedLines: affectedLines,
                    totalLines: CONFIG.TOTAL_LINES,
                    avgTransformTime: avgCPUTime,
                    efficiency: CONFIG.TOTAL_LINES / Math.max(avgCPUTime, 1)
                };
            },

        };

        // 检测WebGL支持
        function checkWebGLSupport() {
            try {
                const canvas = document.createElement('canvas');
                const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
                return !!gl;
            } catch (e) {
                return false;
            }
        }

        if (!checkWebGLSupport()) {
            console.error('❌ WebGL 不支持');
            const warningDiv = document.createElement('div');
            warningDiv.style.cssText = `
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: rgba(255, 122, 0, 0.9);
                color: white;
                padding: 20px;
                border-radius: 10px;
                text-align: center;
                z-index: 1000;
                max-width: 400px;
                border: 3px solid #FF7A00;
            `;
            warningDiv.innerHTML = `
                <h3>❌ WebGL 不支持</h3>
                <p>无法运行CPU Transform版本</p>
                <p>需要支持 WebGL 的浏览器</p>
            `;
            document.body.appendChild(warningDiv);
        }

    </script>
</body>

</html>
        
编辑器加载中
预览
控制台