无人机运动轨迹edit icon

创建者:
用户cHXCtCe7
Fork(复制)
下载
嵌入
BUG反馈
index.html
main.js
package.json
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>无人机轨迹模拟 - A→B→C (带倾斜平板)</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            margin: 0;
            overflow: hidden;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #1a2a6c, #2c3e50);
            color: #fff;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        
        #container {
            position: relative;
            width: 100%;
            height: 100%;
            overflow: hidden;
        }
        
        canvas {
            display: block;
            position: absolute;
            top: 0;
            left: 0;
        }
        
        #info-panel {
            position: absolute;
            top: 20px;
            left: 20px;
            background: rgba(0, 0, 0, 0.7);
            border-radius: 15px;
            padding: 20px;
            max-width: 350px;
            backdrop-filter: blur(5px);
            border: 1px solid rgba(255, 255, 255, 0.1);
            z-index: 10;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
        }
        
        #info-panel h1 {
            margin: 0 0 15px 0;
            color: #4fc3f7;
            font-size: 24px;
            text-align: center;
            border-bottom: 1px solid rgba(255, 255, 255, 0.2);
            padding-bottom: 10px;
        }
        
        .status-box {
            background: rgba(30, 30, 46, 0.8);
            border-radius: 10px;
            padding: 15px;
            margin: 10px 0;
        }
        
        .phase-container {
            margin: 15px 0;
        }
        
        .phase {
            display: flex;
            align-items: center;
            margin: 8px 0;
            padding: 8px;
            border-radius: 5px;
            transition: all 0.3s;
            background: rgba(50, 50, 70, 0.6);
        }
        
        .phase.active {
            background: rgba(41, 98, 255, 0.4);
            transform: translateX(5px);
        }
        
        .phase-indicator {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: #555;
            margin-right: 10px;
            transition: all 0.3s;
        }
        
        .phase.active .phase-indicator {
            background: #00e676;
            box-shadow: 0 0 8px #00e676;
        }
        
        .progress-container {
            height: 10px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 5px;
            margin: 15px 0;
            overflow: hidden;
        }
        
        .progress-bar {
            height: 100%;
            background: linear-gradient(90deg, #00c853, #64ffda);
            border-radius: 5px;
            width: 0%;
            transition: width 0.3s;
        }
        
        .data-panel {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 10px;
            margin-top: 10px;
        }
        
        .data-box {
            background: rgba(0, 0, 0, 0.3);
            border-radius: 8px;
            padding: 10px;
            text-align: center;
        }
        
        .data-title {
            font-size: 12px;
            color: #aaa;
            margin-bottom: 5px;
        }
        
        .data-value {
            font-family: monospace;
            font-size: 16px;
            color: #64ffda;
        }
        
        .controls {
            display: flex;
            gap: 10px;
            margin-top: 15px;
        }
        
        button {
            background: rgba(76, 175, 80, 0.7);
            border: none;
            color: white;
            padding: 12px 0;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.3s;
            flex: 1;
            font-weight: bold;
            letter-spacing: 0.5px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
        }
        
        button:hover {
            background: rgba(76, 175, 80, 1);
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
        }
        
        button:active {
            transform: translateY(1px);
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
        }
        
        #reset-btn {
            background: rgba(244, 67, 54, 0.7);
        }
        
        #reset-btn:hover {
            background: rgba(244, 67, 54, 1);
        }
        
        #speed-control {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-top: 15px;
        }
        
        #speed-slider {
            flex: 1;
            height: 8px;
            border-radius: 4px;
            background: rgba(255, 255, 255, 0.1);
            outline: none;
            -webkit-appearance: none;
        }
        
        #speed-slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 18px;
            height: 18px;
            border-radius: 50%;
            background: #64ffda;
            cursor: pointer;
            box-shadow: 0 0 5px rgba(100, 255, 218, 0.5);
        }
        
        #status-bar {
            position: absolute;
            bottom: 20px;
            left: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.7);
            border-radius: 10px;
            padding: 15px;
            backdrop-filter: blur(5px);
            border: 1px solid rgba(255, 255, 255, 0.1);
            z-index: 10;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
        }
        
        .param-grid {
            display: grid;
            grid-template-columns: repeat(5, 1fr);
            gap: 10px;
            text-align: center;
        }
        
        .param-title {
            font-size: 12px;
            color: #aaa;
        }
        
        .param-value {
            font-family: monospace;
            font-size: 14px;
            color: #64ffda;
        }
        
        .plate-tag {
            position: absolute;
            top: 5px;
            right: 5px;
            background: rgba(255, 100, 50, 0.8);
            padding: 5px 10px;
            border-radius: 10px;
            font-size: 14px;
        }
    </style>
</head>
<body>
    <div id="container">
        <div id="info-panel">
            <h1>无人机轨迹模拟</h1>
            
            <div class="status-box">
                <h3>飞行阶段状态</h3>
                <div class="phase-container">
                    <div class="phase" id="phase1">
                        <div class="phase-indicator"></div>
                        <div>A→B上方 </div>
                    </div>
                    <div class="phase" id="phase2">
                        <div class="phase-indicator"></div>
                        <div>B上方→B点 (下降)</div>
                    </div>
                    <div class="phase" id="phase3">
                        <div class="phase-indicator"></div>
                        <div>B点停留</div>
                    </div>
                    <div class="phase" id="phase4">
                        <div class="phase-indicator"></div>
                        <div>B→C上方 </div>
                    </div>
                    <div class="phase" id="phase5">
                        <div class="phase-indicator"></div>
                        <div>C上方→C点 (下降)</div>
                    </div>
                </div>
                
                <div class="progress-container">
                    <div class="progress-bar" id="progress-bar"></div>
                </div>
                
                <div class="data-panel">
                    <div class="data-box">
                        <div class="data-title">当前位置</div>
                        <div class="data-value" id="position">(0.00, 0.00, 0.00)</div>
                    </div>
                    <div class="data-box">
                        <div class="data-title">飞行速度</div>
                        <div class="data-value" id="velocity">0.00 m/s</div>
                    </div>
                </div>
            </div>
            
            <div class="controls">
                <button id="start-btn">开始模拟</button>
                <button id="pause-btn">暂停</button>
                <button id="reset-btn">重置</button>
            </div>
            
            <div id="speed-control">
                <span>速度: </span>
                <input type="range" id="speed-slider" min="0.1" max="2" step="0.1" value="1">
                <span id="speed-value">1.0x</span>
            </div>
        </div>
        
        <div id="status-bar">
            <div class="param-grid">
                <div>
                    <div class="param-title">A→B上方</div>
                    <div class="param-value">8秒</div>
                </div>
                <div>
                    <div class="param-title">下降时间</div>
                    <div class="param-value">4秒</div>
                </div>
                <div>
                    <div class="param-title">停留时间</div>
                    <div class="param-value">5秒</div>
                </div>
                <div>
                    <div class="param-title">B→C上方</div>
                    <div class="param-value">10秒</div>
                </div>
                <div>
                    <div class="param-title">总时间</div>
                    <div class="param-value">31秒</div>
                </div>
            </div>
        </div>
        
        <div class="plate-tag">C点倾斜平台 (20×20m)</div>
    </div>

    <script>
        // 定义关键点 - 高度调整
        const A = { x: 0, y: 0, z: 0 };
        const B = { x: 50, y: 1, z: 50 };
        const C = { x: 100, y: 10, z: 100 };
        const B_above = { x: 50, y: 10, z: 50 };  // 高度从3改为10
        const C_above = { x: 100, y: 20, z: 100 }; // 高度从12改为20

        // 抛物线最高点高度
        const max_height = 25;

        // 时间参数(秒)
        const t_AB_parabola = 8.0;
        const t_B_descent = 4.0;
        const t_stay = 5.0;
        const t_BC_linear = 10.0;
        const t_C_descent = 4.0;
        const total_time = t_AB_parabola + t_B_descent + t_stay + t_BC_linear + t_C_descent;

        // 动画变量
        let clock = new THREE.Clock();
        let elapsedTime = 0;
        let isPlaying = false;
        let drone, scene, camera, renderer, controls;
        let points = {}, paths = {};
        let animationId;
        let speedFactor = 1.0;

        // 初始化场景
        function init() {
            // 创建场景
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x0a0a1a);
            scene.fog = new THREE.Fog(0x0a0a1a, 50, 200);

            // 创建相机
            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(70, 120, 70);
            camera.lookAt(50, 0, 50);

            // 创建渲染器
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(window.devicePixelRatio);
            document.getElementById('container').appendChild(renderer.domElement);

            // 添加轨道控制
            controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            controls.minDistance = 20;
            controls.maxDistance = 300;

            // 添加光源
            const ambientLight = new THREE.AmbientLight(0x404040, 2);
            scene.add(ambientLight);

            const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
            directionalLight.position.set(1, 1, 1);
            scene.add(directionalLight);

            // 添加坐标轴
            const axesHelper = new THREE.AxesHelper(20);
            scene.add(axesHelper);

            // 创建网格地面
            const gridHelper = new THREE.GridHelper(200, 20, 0x444444, 0x222222);
            scene.add(gridHelper);

            // 创建高度标记
            createHeightMarkers();

            // 创建关键点标记
            createPoint(A, 0xff5555, 'A (0,0,0)', 1.5);
            createPoint(B, 0x55ff55, 'B (50,50,1)', 1.5);
            createPoint(C, 0x5555ff, 'C (100,100,10)', 1.5);
            createPoint(B_above, 0xffff55, `B上方 (50,50,${B_above.y})`, 1.0);
            createPoint(C_above, 0xff55ff, `C上方 (100,100,${C_above.y})`, 1.0);

            // 创建无人机
            createDrone();

            // 创建路径
            createPath(A, B_above, 0xff5555, 'A→B上方 (抛物线)');
            createPath(B_above, B, 0x55ff55, 'B上方→B点 (下降)');
            createPath(B, C_above, 0x5555ff, 'B→C上方 (直线)');
            createPath(C_above, C, 0xff55ff, 'C上方→C点 (下降)');
            
            // 在C点创建倾斜平板模型
            createTiltedPlateAtC();

            // 添加窗口大小调整事件
            window.addEventListener('resize', onWindowResize);
            
            // 添加按钮事件
            document.getElementById('start-btn').addEventListener('click', startSimulation);
            document.getElementById('pause-btn').addEventListener('click', pauseSimulation);
            document.getElementById('reset-btn').addEventListener('click', resetSimulation);
            document.getElementById('speed-slider').addEventListener('input', updateSpeed);
            
            // 开始动画
            animate();
        }
        
        // 在C点创建倾斜平板模型
        function createTiltedPlateAtC() {
            const plateSize = 20; // 20×20米
            
            // 创建平板几何体
            const geometry = new THREE.PlaneGeometry(plateSize, plateSize, 20, 20);
            
            // 创建材质 - 半透明的网格效果
            const material = new THREE.MeshPhongMaterial({
                color: 0x2980b9,
                transparent: true,
                opacity: 0.8,
                wireframe: true,
                side: THREE.DoubleSide
            });
            
            // 创建平板网格
            const plate = new THREE.Mesh(geometry, material);
            
            // 定位在C点上方 (平台中心与C点重合)
            plate.position.set(C.x, C.y, C.z);
            
            // 应用旋转使平板在xz平面倾斜
            // 1. 首先使平板旋转到水平位置(默认是垂直的)
            plate.rotateX(Math.PI / 2);
            
            // 2. 再绕z轴旋转15度(在xy平面投影与x轴正方向呈15度夹角)
            plate.rotateZ(15 * Math.PI / 180);
            
            // 添加边框轮廓增强视觉效果
            const edges = new THREE.EdgesGeometry(geometry);
            const lineMaterial = new THREE.LineBasicMaterial({ color: 0x1abc9c, linewidth: 2 });
            const edgeLines = new THREE.LineSegments(edges, lineMaterial);
            edgeLines.position.set(C.x, C.y, C.z);
            edgeLines.rotateX(Math.PI / 2);
            edgeLines.rotateZ(15 * Math.PI / 180);
            
            // 添加到场景
            scene.add(plate);
            scene.add(edgeLines);
            
            // 添加标签
            createMarker(C.x, C.y + 2, C.z, 0x3498db, "C点平台");
        }

        // 创建高度标记
        function createHeightMarkers() {
            // 抛物线顶点标记
            const midX = (A.x + B_above.x) / 2;
            const midZ = (A.z + B_above.z) / 2;
            createMarker(midX, max_height, midZ, 0x00ff00, `顶点 (25m)`);
            
            // 创建基准高度标记
            createMarker(10, 0, -10, 0xffffff, "0m");
            createMarker(10, 10, -10, 0xffff00, "10m");
            createMarker(10, 20, -10, 0xff00ff, "20m");
            createMarker(10, 25, -10, 0x00ff00, "25m");
        }
        
        function createMarker(x, y, z, color, label) {
            const sphereGeometry = new THREE.SphereGeometry(0.8, 16, 16);
            const sphereMaterial = new THREE.MeshBasicMaterial({ color: color });
            const marker = new THREE.Mesh(sphereGeometry, sphereMaterial);
            marker.position.set(x, y, z);
            scene.add(marker);
            
            const canvas = document.createElement('canvas');
            const context = canvas.getContext('2d');
            canvas.width = 256;
            canvas.height = 64;
            context.fillStyle = 'rgba(0, 0, 0, 0.7)';
            context.fillRect(0, 0, canvas.width, canvas.height);
            context.font = '24px Arial';
            context.fillStyle = 'white';
            context.textAlign = 'center';
            context.fillText(label, canvas.width/2, canvas.height/2 + 8);
            
            const texture = new THREE.CanvasTexture(canvas);
            const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
            const sprite = new THREE.Sprite(spriteMaterial);
            sprite.position.set(x, y + 1.5, z);
            sprite.scale.set(10, 2.5, 1);
            scene.add(sprite);
        }

        // 创建无人机(方体机身,螺旋桨绕Y轴旋转)
        function createDrone() {
            const droneGroup = new THREE.Group();
            
            // 方体机身(长方体)
            const bodyGeometry = new THREE.BoxGeometry(2, 0.8, 2);
            const bodyMaterial = new THREE.MeshPhongMaterial({
                color: 0x00ffff,
                emissive: 0x004444,
                specular: 0xffffff,
                shininess: 100,
                wireframe: false
            });
            const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
            droneGroup.add(body);
            
            // 添加无人机螺旋桨(绕Y轴旋转)
            const propellerGroup = new THREE.Group();
            // 四个螺旋桨的位置(正方形的四个角)
            const propellerPositions = [
                { x: 1.5, y: 0, z: 1.5 },
                { x: -1.5, y: 0, z: 1.5 },
                { x: 1.5, y: 0, z: -1.5 },
                { x: -1.5, y: 0, z: -1.5 }
            ];
            
            propellerPositions.forEach(pos => {
                const propeller = new THREE.Mesh(
                    new THREE.CylinderGeometry(0.25, 0.25, 0.1, 16),
                    new THREE.MeshPhongMaterial({ color: 0xaaaaaa })
                );
                
                // 初始旋转使螺旋桨平行于XZ平面
                propeller.rotation.x = Math.PI / 2;
                propeller.position.set(pos.x, pos.y + 0.4, pos.z);
                propellerGroup.add(propeller);
            });
            
            droneGroup.add(propellerGroup);
            droneGroup.position.set(A.x, A.y, A.z);
            scene.add(droneGroup);
            
            drone = droneGroup;
        }

        // 创建关键点标记
        function createPoint(position, color, label, size = 1) {
            const geometry = new THREE.SphereGeometry(size, 16, 16);
            const material = new THREE.MeshPhongMaterial({ 
                color: color,
                emissive: color,
                emissiveIntensity: 0.2
            });
            const sphere = new THREE.Mesh(geometry, material);
            sphere.position.set(position.x, position.y, position.z);
            scene.add(sphere);
            
            // 添加标签
            const canvas = document.createElement('canvas');
            const context = canvas.getContext('2d');
            canvas.width = 256;
            canvas.height = 64;
            context.fillStyle = 'rgba(0, 0, 0, 0.7)';
            context.fillRect(0, 0, canvas.width, canvas.height);
            context.font = '24px Arial';
            context.fillStyle = 'white';
            context.textAlign = 'center';
            context.fillText(label, canvas.width/2, canvas.height/2 + 8);
            
            const texture = new THREE.CanvasTexture(canvas);
            const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
            const sprite = new THREE.Sprite(spriteMaterial);
            sprite.position.set(position.x, position.y, position.z + size + 2);
            sprite.scale.set(10, 2.5, 1);
            scene.add(sprite);
            
            // 保存点以便后续引用
            points[label] = sphere;
        }

        // 创建路径
        function createPath(start, end, color, name) {
            const points = [];
            const material = new THREE.LineBasicMaterial({ 
                color: color,
                linewidth: 2
            });
            
            if (name === 'A→B上方 (抛物线)') {
                // 抛物线路径,最高点25米
                for (let t = 0; t <= 1; t += 0.05) {
                    const x = start.x + t * (end.x - start.x);
                    const y = start.y + t * (end.y - start.y) + 4 * max_height * t * (1 - t);
                    const z = start.z + t * (end.z - start.z);
                    points.push(new THREE.Vector3(x, y, z));
                }
            } else {
                // 直线路径
                for (let t = 0; t <= 1; t += 0.05) {
                    const x = start.x + t * (end.x - start.x);
                    const y = start.y + t * (end.y - start.y);
                    const z = start.z + t * (end.z - start.z);
                    points.push(new THREE.Vector3(x, y, z));
                }
            }
            
            const geometry = new THREE.BufferGeometry().setFromPoints(points);
            const line = new THREE.Line(geometry, material);
            scene.add(line);
            
            // 保存路径
            paths[name] = line;
        }

        // 计算无人机位置
        function getDronePosition(time) {
            // A→B上方的抛物线运动
            if (time < t_AB_parabola) {
                setActivePhase(1);
                const t = time / t_AB_parabola;
                const x = A.x + t * (B_above.x - A.x);
                const y = A.y + t * (B_above.y - A.y) + 4 * max_height * t * (1 - t);
                const z = A.z + t * (B_above.z - A.z);
                return { x, y, z };
            }
            // B上方→B点的缓慢下降
            else if (time < t_AB_parabola + t_B_descent) {
                setActivePhase(2);
                const t = (time - t_AB_parabola) / t_B_descent;
                const x = B_above.x + t * (B.x - B_above.x);
                const y = B_above.y + t * (B.y - B_above.y);
                const z = B_above.z + t * (B.z - B_above.z);
                return { x, y, z };
            }
            // B点停留
            else if (time < t_AB_parabola + t_B_descent + t_stay) {
                setActivePhase(3);
                return { x: B.x, y: B.y, z: B.z };
            }
            // B→C上方的直线运动
            else if (time < t_AB_parabola + t_B_descent + t_stay + t_BC_linear) {
                setActivePhase(4);
                const t = (time - (t_AB_parabola + t_B_descent + t_stay)) / t_BC_linear;
                const x = B.x + t * (C_above.x - B.x);
                const y = B.y + t * (C_above.y - B.y);
                const z = B.z + t * (C_above.z - B.z);
                return { x, y, z };
            }
            // C上方→C点的缓慢下降
            else {
                setActivePhase(5);
                const t = (time - (t_AB_parabola + t_B_descent + t_stay + t_BC_linear)) / t_C_descent;
                const x = C_above.x + t * (C.x - C_above.x);
                const y = C_above.y + t * (C.y - C_above.y);
                const z = C_above.z + t * (C.z - C_above.z);
                return { x, y, z };
            }
        }

        // 设置活动阶段
        function setActivePhase(phaseNum) {
            // 重置所有阶段
            for (let i = 1; i <= 5; i++) {
                const phaseElement = document.getElementById(`phase${i}`);
                phaseElement.classList.remove('active');
            }
            
            // 激活当前阶段
            const currentPhase = document.getElementById(`phase${phaseNum}`);
            if (currentPhase) {
                currentPhase.classList.add('active');
            }
        }

        // 开始模拟
        function startSimulation() {
            if (!isPlaying) {
                isPlaying = true;
                clock.start();
            }
        }

        // 暂停模拟
        function pauseSimulation() {
            isPlaying = false;
            clock.stop();
        }

        // 重置模拟
        function resetSimulation() {
            isPlaying = false;
            elapsedTime = 0;
            clock.stop();
            clock = new THREE.Clock();
            drone.position.set(A.x, A.y, A.z);
            document.getElementById('progress-bar').style.width = '0%';
            document.getElementById('position').textContent = '(0.00, 0.00, 0.00)';
            document.getElementById('velocity').textContent = '0.00 m/s';
            
            // 重置所有阶段
            for (let i = 1; i <= 5; i++) {
                const phaseElement = document.getElementById(`phase${i}`);
                phaseElement.classList.remove('active');
            }
        }

        // 更新速度
        function updateSpeed() {
            speedFactor = parseFloat(document.getElementById('speed-slider').value);
            document.getElementById('speed-value').textContent = speedFactor.toFixed(1) + 'x';
        }

        // 窗口大小调整
        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        // 动画循环
        function animate() {
            animationId = requestAnimationFrame(animate);
            
            if (isPlaying) {
                const delta = Math.min(clock.getDelta(), 0.1) * speedFactor;
                elapsedTime += delta;
                
                // 更新无人机位置
                if (elapsedTime <= total_time) {
                    const position = getDronePosition(elapsedTime);
                    drone.position.set(position.x, position.y, position.z);
                    
                    // 更新UI
                    document.getElementById('progress-bar').style.width = `${(elapsedTime / total_time) * 100}%`;
                    document.getElementById('position').textContent = 
                        `(${position.x.toFixed(2)}, ${position.y.toFixed(2)}, ${position.z.toFixed(2)})`;
                    
                    // 计算速度(简单近似)
                    const prevPosition = getDronePosition(Math.max(0, elapsedTime - delta));
                    const distance = Math.sqrt(
                        Math.pow(position.x - prevPosition.x, 2) +
                        Math.pow(position.y - prevPosition.y, 2) +
                        Math.pow(position.z - prevPosition.z, 2)
                    );
                    const speed = distance / delta;
                    document.getElementById('velocity').textContent = `${speed.toFixed(2)} m/s`;
                    
                    // 旋转螺旋桨(绕Y轴)
                    if (drone.children[1]) {
                        drone.children[1].rotation.y += delta * 20;
                    }
                } else {
                    // 模拟结束
                    isPlaying = false;
                }
            }
            
            controls.update();
            renderer.render(scene, camera);
        }

        // 初始化场景
        init();
    </script>
</body>
</html>
        
编辑器加载中
预览
控制台