{
  "case_id": "A4",
  "case_name": "3D鹈鹕骑自行车",
  "prompt": "生成一个3D版本的鹈鹕骑自行车动画，要求：\n1. 使用Three.js实现3D场景\n2. 鹈鹕模型用几何体拼装（体素风格）\n3. 自行车轮子真实转动\n4. 添加地面和天空背景\n5. 单文件HTML",
  "result": {
    "success": true,
    "content": "这是一个单文件HTML解决方案。它使用Three.js库构建了一个3D场景，包含一个由几何体（盒子）拼装成的体素风格鹈鹕，骑着一辆自行车在地面上行驶。\n\n### 代码说明：\n1.  **Three.js 基础**：设置了场景、相机、渲染器和光照（包括产生阴影的平行光）。\n2.  **体素建模**：鹈鹕和自行车都是通过基础几何体（主要是 `BoxGeometry` 和 `CylinderGeometry`）组合而成的。我通过父子层级关系（Group）来管理它们，方便整体移动。\n3.  **动画逻辑**：\n    *   **车轮转动**：在渲染循环中旋转车轮。\n    *   **蹬车动作**：简单的腿部和踏板旋转动画。\n    *   **整体移动**：鹈鹕和自行车在一个巨大的圆形路径上移动。\n4.  **环境**：包含一个棋盘格纹理的地面和淡蓝色的天空背景。\n\n你可以直接将以下代码保存为 `.html` 文件（例如 `pelican_bike.html`），然后在浏览器中打开。\n\n```html\n<!DOCTYPE html>\n<html lang=\"zh\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>3D Voxel Pelican on Bicycle</title>\n    <style>\n        body { margin: 0; overflow: hidden; background-color: #87CEEB; }\n        #info {\n            position: absolute;\n            top: 10px;\n            width: 100%;\n            text-align: center;\n            color: #333;\n            font-family: sans-serif;\n            font-weight: bold;\n            pointer-events: none;\n        }\n    </style>\n</head>\n<body>\n    <div id=\"info\">3D 鹈鹕骑行演示 (Three.js)</div>\n    \n    <!-- 引入 Three.js -->\n    <script type=\"importmap\">\n        {\n            \"imports\": {\n                \"three\": \"https://unpkg.com/three@0.160.0/build/three.module.js\"\n            }\n        }\n    </script>\n\n    <script type=\"module\">\n        import * as THREE from 'three';\n\n        // --- 1. 场景初始化 ---\n        const scene = new THREE.Scene();\n        scene.background = new THREE.Color(0x87CEEB); // 天空颜色\n        scene.fog = new THREE.Fog(0x87CEEB, 20, 100);\n\n        const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);\n        camera.position.set(0, 5, 10);\n        camera.lookAt(0, 1, 0);\n\n        const renderer = new THREE.WebGLRenderer({ antialias: true });\n        renderer.setSize(window.innerWidth, window.innerHeight);\n        renderer.shadowMap.enabled = true;\n        document.body.appendChild(renderer.domElement);\n\n        // --- 2. 灯光 ---\n        const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);\n        scene.add(ambientLight);\n\n        const dirLight = new THREE.DirectionalLight(0xffffff, 1);\n        dirLight.position.set(10, 20, 10);\n        dirLight.castShadow = true;\n        dirLight.shadow.mapSize.width = 2048;\n        dirLight.shadow.mapSize.height = 2048;\n        scene.add(dirLight);\n\n        // --- 3. 地面 ---\n        const planeGeometry = new THREE.PlaneGeometry(200, 200);\n        // 创建棋盘格纹理\n        const canvas = document.createElement('canvas');\n        canvas.width = 64;\n        canvas.height = 64;\n        const context = canvas.getContext('2d');\n        context.fillStyle = '#4CAF50'; // 草地绿\n        context.fillRect(0, 0, 64, 64);\n        context.fillStyle = '#45a049'; // 深绿\n        context.fillRect(0, 0, 32, 32);\n        context.fillRect(32, 32, 32, 32);\n        \n        const texture = new THREE.CanvasTexture(canvas);\n        texture.wrapS = THREE.RepeatWrapping;\n        texture.wrapT = THREE.RepeatWrapping;\n        texture.repeat.set(50, 50);\n        texture.magFilter = THREE.NearestFilter; // 保持像素感\n\n        const planeMaterial = new THREE.MeshStandardMaterial({ map: texture });\n        const plane = new THREE.Mesh(planeGeometry, planeMaterial);\n        plane.rotation.x = -Math.PI / 2;\n        plane.receiveShadow = true;\n        scene.add(plane);\n\n        // --- 4. 构建模型 (体素风格) ---\n        \n        // 辅助函数：创建盒子\n        function createBox(w, h, d, color, x, y, z) {\n            const geometry = new THREE.BoxGeometry(w, h, d);\n            const material = new THREE.MeshStandardMaterial({ color: color });\n            const mesh = new THREE.Mesh(geometry, material);\n            mesh.position.set(x, y, z);\n            mesh.castShadow = true;\n            mesh.receiveShadow = true;\n            return mesh;\n        }\n\n        // 辅助函数：创建圆柱\n        function createCylinder(rt, rb, h, color, x, y, z, rotX = 0, rotZ = 0) {\n            const geometry = new THREE.CylinderGeometry(rt, rb, h, 16);\n            const material = new THREE.MeshStandardMaterial({ color: color });\n            const mesh = new THREE.Mesh(geometry, material);\n            mesh.position.set(x, y, z);\n            mesh.rotation.x = rotX;\n            mesh.rotation.z = rotZ;\n            mesh.castShadow = true;\n            return mesh;\n        }\n\n        // 主容器：包含鹈鹕和自行车\n        const riderGroup = new THREE.Group();\n        scene.add(riderGroup);\n\n        // === 自行车部分 ===\n        const bikeGroup = new THREE.Group();\n        riderGroup.add(bikeGroup);\n\n        const metalColor = 0x333333;\n        const frameColor = 0xD32F2F; // 红色车架\n\n        // 车轮\n        const wheelGeo = new THREE.TorusGeometry(0.8, 0.1, 8, 20);\n        const wheelMat = new THREE.MeshStandardMaterial({ color: 0x111111 });\n        \n        const frontWheel = new THREE.Mesh(wheelGeo, wheelMat);\n        frontWheel.position.set(0, 0.8, 1.8);\n        frontWheel.castShadow = true;\n        bikeGroup.add(frontWheel);\n\n        // 轮辐 (简单的十字)\n        const spokeGeo = new THREE.BoxGeometry(1.4, 0.05, 0.05);\n        const spokeMat = new THREE.MeshStandardMaterial({ color: 0x888888 });\n        const spoke1 = new THREE.Mesh(spokeGeo, spokeMat);\n        const spoke2 = new THREE.Mesh(spokeGeo, spokeMat); spoke2.rotation.z = Math.PI/2;\n        frontWheel.add(spoke1.clone());\n        frontWheel.add(spoke2.clone());\n\n        const backWheel = new THREE.Mesh(wheelGeo, wheelMat);\n        backWheel.position.set(0, 0.8, -1.8);\n        backWheel.castShadow = true;\n        bikeGroup.add(backWheel);\n        backWheel.add(spoke1.clone());\n        backWheel.add(spoke2.clone());\n\n        // 车架 (简化版)\n        const frame1 = createCylinder(0.08, 0.08, 3.6, frameColor, 0, 0.8, 0, Math.PI/2); // 连接轮轴\n        bikeGroup.add(frame1);\n        \n        const seatPost = createCylinder(0.08, 0.08, 1.5, frameColor, 0, 1.5, -0.5, -0.2);\n        bikeGroup.add(seatPost);\n\n        const handlePost = createCylinder(0.08, 0.08, 1.6, frameColor, 0, 1.6, 1.4, -0.3);\n        bikeGroup.add(handlePost);\n\n        const crossBar = createCylinder(0.07, 0.07, 2.0, frameColor, 0, 1.8, 0.4, Math.PI/2); // 横梁\n        bikeGroup.add(crossBar);\n\n        // 车把\n        const handleBar = createCylinder(0.06, 0.06, 1.2, 0xSilver, 0, 2.3, 1.2, 0, Math.PI/2);\n        bikeGroup.add(handleBar);\n\n        // 车座\n        const seat = createBox(0.6, 0.1, 0.8, 0x222222, 0, 2.2, -0.6);\n        bikeGroup.add(seat);\n\n        // 踏板轴心\n        const pedalAxis = new THREE.Group();\n        pedalAxis.position.set(0, 0.8, 0);\n        bikeGroup.add(pedalAxis);\n\n        // === 鹈鹕部分 (体素风格) ===\n        const pelicanGroup = new THREE.Group();\n        pelicanGroup.position.set(0, 2.3, -0.6); // 坐在车座上\n        riderGroup.add(pelicanGroup);\n\n        const whiteFeather = 0xFFFFFF;\n        const beakColor = 0xFFA500; // 橙色\n        const legColor = 0xFFA500;\n\n        // 身体\n        const body = createBox(0.9, 1.0, 1.4, whiteFeather, 0, 0.5, 0);\n        pelicanGroup.add(body);\n\n        // 尾巴\n        const tail = createBox(0.6, 0.2, 0.8, whiteFeather, 0, 0.6, -1.0);\n        tail.rotation.x = -0.3;\n        pelicanGroup.add(tail);\n\n        // 脖子 (几个方块堆叠)\n        const neckGroup = new THREE.Group();\n        neckGroup.position.set(0, 1.0, 0.5);\n        pelicanGroup.add(neckGroup);\n        \n        const neck1 = createBox(0.4, 0.4, 0.4, whiteFeather, 0, 0.2, 0);\n        const neck2 = createBox(0.38, 0.4, 0.38, whiteFeather, 0, 0.6, 0.1);\n        const neck3 = createBox(0.36, 0.4, 0.36, whiteFeather, 0, 1.0, 0.2);\n        neckGroup.add(neck1, neck2, neck3);\n\n        // 头\n        const head = createBox(0.5, 0.5, 0.6, whiteFeather, 0, 1.4, 0.3);\n        neckGroup.add(head);\n\n        // 眼睛\n        const eyeL = createBox(0.05, 0.05, 0.05, 0x000000, 0.26, 1.5, 0.4);\n        const eyeR = createBox(0.05, 0.05, 0.05, 0x000000, -0.26, 1.5, 0.4);\n        neckGroup.add(eyeL, eyeR);\n\n        // 标志性的大嘴 (喙)\n        const upperBeak = createBox(0.3, 0.1, 1.2, beakColor, 0, 1.45, 1.2);\n        neckGroup.add(upperBeak);\n\n        const pouch = createBox(0.3, 0.3, 1.0, 0xFFD700, 0, 1.25, 1.1); // 喉囊 (黄色)\n        // 让喉囊看起来圆润一点（通过缩小底部）\n        pouch.scale.set(1, 1, 1); \n        neckGroup.add(pouch);\n\n        // 翅膀\n        const wingL = createBox(0.1, 0.8, 1.0, 0xEEEEEE, 0.5, 0.6, 0.1);\n        wingL.rotation.z = -0.2;\n        const wingR = createBox(0.1, 0.8, 1.0, 0xEEEEEE, -0.5, 0.6, 0.1);\n        wingR.rotation.z = 0.2;\n        pelicanGroup.add(wingL, wingR);\n\n        // 腿 (用于动画)\n        const legLGroup = new THREE.Group();\n        legLGroup.position.set(0.3, 0.1, 0);\n        pelicanGroup.add(legLGroup);\n        const thighL = createBox(0.2, 0.8, 0.2, whiteFeather, 0, -0.4, 0.2);\n        thighL.rotation.x = -0.5;\n        legLGroup.add(thighL);\n        const calfL = createBox(0.15, 0.8, 0.15, legColor, 0, -1.0, 0.4);\n        legLGroup.add(calfL);\n\n        const legRGroup = new THREE.Group();\n        legRGroup.position.set(-0.3, 0.1, 0);\n        pelicanGroup.add(legRGroup);\n        const thighR = createBox(0.2, 0.8, 0.2, whiteFeather, 0, -0.4, 0.2);\n        thighR.rotation.x = -0.5;\n        legRGroup.add(thighR);\n        const calfR = createBox(0.15, 0.8, 0.15, legColor, 0, -1.0, 0.4);\n        legRGroup.add(calfR);\n\n        // 踏板\n        const pedalL = createBox(0.3, 0.05, 0.2, 0x333333, 0.4, 0, 0);\n        const pedalR = createBox(0.3, 0.05, 0.2, 0x333333, -0.4, 0, 0);\n        pedalAxis.add(pedalL);\n        pedalAxis.add(pedalR);\n\n        // --- 5. 动画循环 ---\n        \n        let angle = 0;\n        const radius = 15;\n        const speed = 0.01;\n\n        function animate() {\n            requestAnimationFrame(animate);\n\n            // 1. 整体路径移动 (绕圈圈)\n            angle += speed;\n            riderGroup.position.x = Math.cos(angle) * radius;\n            riderGroup.position.z = Math.sin(angle) * radius;\n            riderGroup.rotation.y = -angle; // 面向切线方向\n\n            // 2. 车轮转动\n            // 速度 = 角速度 * 半径。这里模拟一个大概的视觉速度\n            const wheelSpeed = 0.15;\n            frontWheel.rotation.x -= wheelSpeed;\n            backWheel.rotation.x -= wheelSpeed;\n\n            // 3. 踏板转动\n            pedalAxis.rotation.x -= wheelSpeed;\n            \n            // 保持踏板水平\n            pedalL.rotation.x += wheelSpeed;\n            pedalR.rotation.x += wheelSpeed;\n            \n            // 4. 计算踏板位置以移动腿部\n            // 简单模拟：根据踏板角度调整小腿和脚的位置\n            // 左腿\n            const pedalAngle = pedalAxis.rotation.x;\n            \n            // 简单的腿部上下摆动逻辑\n            const legOffset = 0.3;\n            legLGroup.position.y = 0.1 + Math.sin(pedalAngle) * legOffset;\n            legLGroup.position.z = Math.cos(pedalAngle) * legOffset * 0.5;\n            \n            // 右腿 (相位差 PI)\n            legRGroup.position.y = 0.1 + Math.sin(pedalAngle + Math.PI) * legOffset;\n            legRGroup.position.z = Math.cos(pedalAngle + Math.PI) * legOffset * 0.5;\n\n            // 5. 鹈鹕身体轻微起伏 (呼吸/骑行感)\n            pelicanGroup.position.y = 2.3 + Math.sin(angle * 10) * 0.02;\n            \n            // 6. 相机跟随\n            // 相机位置设置在骑手侧后方\n            const camOffsetX = Math.cos(angle + 0.5) * (radius + 10);\n            const camOffsetZ = Math.sin(angle + 0.5) * (radius + 10);\n            camera.position.x = camOffsetX;\n            camera.position.z = camOffsetZ;\n            camera.lookAt(riderGroup.position);\n\n            renderer.render(scene, camera);\n        }\n\n        // 窗口大小调整\n        window.addEventListener('resize', () => {\n            camera.aspect = window.innerWidth / window.innerHeight;\n            camera.updateProjectionMatrix();\n            renderer.setSize(window.innerWidth, window.innerHeight);\n        });\n\n        animate();\n    </script>\n</body>\n</html>\n```",
    "model": "gemini-3-pro-preview",
    "usage": {
      "prompt_tokens": 91,
      "completion_tokens": 5994,
      "total_tokens": 6085
    },
    "timestamp": "2025-11-19 13:26:30"
  },
  "needs_screenshot": true,
  "needs_manual_test": false
}