Files
2026-08-16 14:58:47 +08:00

664 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 3D Visualization Content Generator
Generate a self-contained HTML 3D visualization with embedded widget configuration using Three.js.
## Output Structure
Your output must be a complete HTML document with:
1. **Standard HTML5 structure**
2. **Three.js loaded from CDN** (use unpkg or cdnjs)
3. **Embedded widget configuration** in a `<script type="application/json" id="widget-config">` tag
4. **3D scene with interactive controls** (OrbitControls, sliders, buttons, **ZOOM BUTTONS**)
5. **Mobile-responsive design**
6. **postMessage listener** for widget actions (REQUIRED)
## ⚠️ CRITICAL REQUIREMENTS
### 1. LIGHTING - Objects MUST be clearly visible
**ALWAYS ensure:**
- Background should NOT be pure black (use deep blue `#0a0a1a` or dark gradient)
- Ambient light intensity at least `0.4` (not 0.1!)
- Main objects MUST have dedicated lights illuminating them
- For planets/Earth, use bright diffuse color (not dark!)
- Add hemisphere light for natural ambient fill
```javascript
// GOOD lighting setup
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
// Hemisphere light for natural lighting
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6);
scene.add(hemiLight);
// Main directional light
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
directionalLight.position.set(10, 20, 10);
scene.add(directionalLight);
```
### 2. ZOOM CONTROLS - REQUIRED for mobile users
**MUST include zoom buttons** in the control panel:
```html
<!-- Add these buttons to your controls -->
<div class="zoom-controls">
<button id="zoom-in-btn" title="放大">+</button>
<button id="zoom-out-btn" title="缩小"></button>
</div>
```
```javascript
// Zoom functionality
document.getElementById('zoom-in-btn').addEventListener('click', () => {
const direction = new THREE.Vector3();
camera.getWorldDirection(direction);
camera.position.addScaledVector(direction, 5);
});
document.getElementById('zoom-out-btn').addEventListener('click', () => {
const direction = new THREE.Vector3();
camera.getWorldDirection(direction);
camera.position.addScaledVector(direction, -5);
});
```
### 3. REALISTIC OBJECTS - Use procedural textures
**For Earth/planets, create realistic appearance:**
```javascript
// Create procedural Earth texture with continents
function createEarthTexture() {
const canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 256;
const ctx = canvas.getContext('2d');
// Ocean base (bright blue, not dark!)
ctx.fillStyle = '#1e90ff';
ctx.fillRect(0, 0, 512, 256);
// Add continents (green land masses)
ctx.fillStyle = '#228b22';
// Simple continent shapes (approximate)
// North America
ctx.beginPath();
ctx.ellipse(100, 80, 60, 40, 0, 0, Math.PI * 2);
ctx.fill();
// South America
ctx.beginPath();
ctx.ellipse(130, 160, 30, 50, 0.3, 0, Math.PI * 2);
ctx.fill();
// Europe/Africa
ctx.beginPath();
ctx.ellipse(270, 100, 40, 70, 0, 0, Math.PI * 2);
ctx.fill();
// Asia
ctx.beginPath();
ctx.ellipse(380, 70, 80, 50, 0, 0, Math.PI * 2);
ctx.fill();
// Australia
ctx.beginPath();
ctx.ellipse(420, 170, 30, 20, 0, 0, Math.PI * 2);
ctx.fill();
// Add ice caps
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, 512, 15);
ctx.fillRect(0, 241, 512, 15);
// Add clouds (light patches)
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
for (let i = 0; i < 20; i++) {
const x = Math.random() * 512;
const y = Math.random() * 256;
ctx.beginPath();
ctx.ellipse(x, y, 30 + Math.random() * 20, 10 + Math.random() * 10, 0, 0, Math.PI * 2);
ctx.fill();
}
return new THREE.CanvasTexture(canvas);
}
// Create Earth with procedural texture
const earthGeometry = new THREE.SphereGeometry(1, 64, 64);
const earthMaterial = new THREE.MeshPhongMaterial({
map: createEarthTexture(),
specular: 0x333333,
shininess: 15
});
const earth = new THREE.Mesh(earthGeometry, earthMaterial);
```
**For other planets:**
- **Mars**: Red-orange with dark patches (`#cd5c5c` base, `#8b4513` patches)
- **Jupiter**: Orange bands with white ovals
- **Sun**: Bright yellow-orange with glow effect (use emissive material)
- **Moon**: Gray with craters (use noise pattern)
## Widget Config Schema
```json
{
"type": "visualization3d",
"visualizationType": "solar",
"description": "Interactive solar system model",
"objects": [
{ "id": "sun", "type": "sphere", "material": { "type": "emissive", "color": "#FDB813" } },
{ "id": "earth", "type": "sphere", "material": { "type": "textured", "textureType": "earth" } }
],
"interactions": [
{ "type": "orbit", "target": "camera" },
{ "type": "slider", "param": "speed", "min": 0, "max": 10, "default": 1 },
{ "type": "button", "action": "zoomIn", "label": "放大" },
{ "type": "button", "action": "zoomOut", "label": "缩小" }
],
"presets": [
{ "name": "View Earth", "state": { "cameraTarget": "earth" } }
]
}
```
## Three.js Setup Template (Complete with Safeguards)
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Visualization</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
/* CRITICAL: Set body background to match scene - fallback if Three.js fails */
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background: #0a0a1a; /* MUST match scene.background color! */
}
#canvas-container { width: 100%; height: 100%; position: relative; }
canvas { display: block; }
/* Loading overlay - shows while Three.js initializes */
#loading {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: #0a0a1a;
display: flex;
align-items: center;
justify-content: center;
color: #aaa;
font-size: 16px;
z-index: 1000;
}
#loading .spinner {
width: 40px; height: 40px;
border: 3px solid #333;
border-top-color: #6366f1;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Control panel - mobile friendly */
#controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(20, 20, 30, 0.9);
backdrop-filter: blur(12px);
padding: 16px;
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
align-items: center;
border-top: 1px solid rgba(255,255,255,0.1);
}
.control-group {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 100px;
}
label {
font-size: 11px;
color: #aaa;
text-transform: uppercase;
letter-spacing: 0.5px;
}
input[type="range"] {
width: 100%;
height: 6px;
-webkit-appearance: none;
background: #333;
border-radius: 3px;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: #6366f1;
border-radius: 50%;
cursor: pointer;
}
button {
padding: 12px 20px;
border: none;
border-radius: 8px;
background: #333;
color: white;
cursor: pointer;
font-size: 14px;
min-width: 44px;
min-height: 44px;
transition: all 0.2s;
}
button:hover { background: #444; }
button:active { transform: scale(0.95); }
button.primary { background: #6366f1; }
button.primary:hover { background: #5558e8; }
/* Zoom buttons side by side */
.zoom-btns {
display: flex;
gap: 8px;
}
.zoom-btns button {
width: 44px;
height: 44px;
font-size: 24px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
/* Info panel */
#info {
position: absolute;
top: 20px;
left: 20px;
background: rgba(20, 20, 30, 0.85);
backdrop-filter: blur(8px);
padding: 16px;
border-radius: 12px;
max-width: 280px;
border: 1px solid rgba(255,255,255,0.1);
}
#info h2 {
font-size: 16px;
color: #fbbf24;
margin-bottom: 8px;
}
#info p {
font-size: 13px;
color: #ccc;
line-height: 1.5;
}
@media (max-width: 600px) {
#info { display: none; }
#controls { padding: 12px 8px 24px; }
}
</style>
</head>
<body>
<!-- Loading overlay - REQUIRED -->
<div id="loading">
<div style="text-align:center;">
<div class="spinner"></div>
Loading 3D Scene...
</div>
</div>
<div id="canvas-container"></div>
<div id="info">
<h2>Scene Title</h2>
<p>Description text here.</p>
</div>
<div id="controls">
<div class="control-group">
<label>Speed</label>
<input type="range" id="speed-slider" min="0" max="5" step="0.1" value="1">
</div>
<div class="zoom-btns">
<button id="zoom-in-btn" title="Zoom In">+</button>
<button id="zoom-out-btn" title="Zoom Out"></button>
</div>
<button id="reset-btn" class="primary">Reset</button>
</div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// WebGL support check - REQUIRED
function checkWebGL() {
try {
const canvas = document.createElement('canvas');
return !!(window.WebGLRenderingContext &&
(canvas.getContext('webgl') || canvas.getContext('experimental-webgl')));
} catch(e) {
return false;
}
}
// Scene initialization with error handling - REQUIRED
async function initScene() {
try {
// Check WebGL support
if (!checkWebGL()) {
throw new Error('WebGL not supported in this browser');
}
const container = document.getElementById('canvas-container');
// Validate container dimensions - REQUIRED
const width = container.clientWidth || window.innerWidth;
const height = container.clientHeight || window.innerHeight;
if (width === 0 || height === 0) {
throw new Error('Container has zero dimensions');
}
// Scene setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a1a); // MUST match body background!
// Camera with validated dimensions
const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
camera.position.set(0, 5, 15);
// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
container.appendChild(renderer.domElement);
// OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// GOOD lighting setup - objects must be visible!
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6);
scene.add(hemiLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
directionalLight.position.set(10, 20, 10);
scene.add(directionalLight);
// Objects storage for later reference
const objects = {};
// Animation state
let animationSpeed = 1;
// Animation loop
function animate() {
requestAnimationFrame(animate);
// Update animations...
controls.update();
renderer.render(scene, camera);
}
animate();
// Zoom controls - REQUIRED for mobile
document.getElementById('zoom-in-btn').addEventListener('click', () => {
const direction = new THREE.Vector3();
camera.getWorldDirection(direction);
camera.position.addScaledVector(direction, 3);
});
document.getElementById('zoom-out-btn').addEventListener('click', () => {
const direction = new THREE.Vector3();
camera.getWorldDirection(direction);
camera.position.addScaledVector(direction, -3);
});
// Reset button
document.getElementById('reset-btn').addEventListener('click', () => {
camera.position.set(0, 5, 15);
controls.target.set(0, 0, 0);
});
// Handle resize
window.addEventListener('resize', () => {
const newWidth = container.clientWidth || window.innerWidth;
const newHeight = container.clientHeight || window.innerHeight;
camera.aspect = newWidth / newHeight;
camera.updateProjectionMatrix();
renderer.setSize(newWidth, newHeight);
});
// Hide loading overlay - scene is ready
document.getElementById('loading').style.display = 'none';
} catch (error) {
console.error('Scene initialization failed:', error);
// Show error message in loading overlay
document.getElementById('loading').innerHTML =
`<div style="text-align:center;color:#ff6b6b;">
<div style="font-size:24px;margin-bottom:16px;">⚠️</div>
Failed to load 3D scene<br>
<small style="color:#888;">${error.message}</small><br>
<button onclick="location.reload()" style="margin-top:16px;padding:8px 16px;background:#6366f1;color:white;border:none;border-radius:6px;cursor:pointer;">Retry</button>
</div>`;
}
}
// Initialize scene
initScene();
</script>
<script type="application/json" id="widget-config">
{
"type": "visualization3d",
"visualizationType": "custom",
"description": "3D visualization",
"objects": [],
"interactions": []
}
</script>
</body>
</html>
```
## Visualization Types
### 1. Solar System (`solar`)
- Sun with emissive glow effect
- Planets with **procedural textures** (Earth with continents, Mars red, etc.)
- Orbital paths visible
- Zoom controls for mobile
- Bright lighting so planets are visible
### 2. Molecular (`molecular`)
- Atoms as colored spheres with high contrast
- Bonds as cylinders
- Labels for atom types
- Good ambient lighting
### 3. Anatomy (`anatomy`)
- Organs with distinct colors
- Transparent layers
- Labels and descriptions
### 4. Geometry (`geometry`)
- 3D shapes with distinct colors
- Edge highlighting
- Measurement annotations
### 5. Physics (`physics`)
- Trajectories with visible paths
- Force arrows
- Clear contrast between objects
### 6. Custom (`custom`)
- Follow the same lighting and zoom requirements
## Design Requirements
### 1. Visibility & Contrast
- Background: Use `#0a0a1a` or dark gradient (NOT pure black)
- Objects: Use bright, distinct colors
- Ambient light: At least 0.5 intensity
- Add hemisphere light for natural fill
### 2. Mobile Responsiveness
- Touch-friendly controls (44px minimum)
- Zoom buttons always visible
- OrbitControls works with touch
- Control panel at bottom for thumb access
### 3. Performance
- Use `requestAnimationFrame`
- Limit geometry complexity
- Use 64 segments for spheres (not 128)
### 4. Textures
- Create procedural textures using Canvas API
- No external image dependencies
- Earth: Blue ocean + green continents + white ice caps
- Planets: Appropriate colors with variations
## JavaScript Coding Rules
### 1. Switch Statement Scope (CRITICAL - Causes SyntaxError)
**WRONG - Variables redeclared across cases:**
```javascript
// This causes: SyntaxError: Identifier 'elementId' has already been declared
switch (action) {
case 'HIGHLIGHT_ELEMENT':
const { elementId, highlight } = payload; // First const
// ...
break;
case 'ANNOTATE_ELEMENT':
const { elementId, text } = payload; // ERROR! elementId already declared
// ...
break;
}
```
**CORRECT - Wrap each case in braces to create block scope:**
```javascript
// Each case has its own block scope
switch (action) {
case 'HIGHLIGHT_ELEMENT': {
const { elementId, highlight } = payload;
// ...
break;
}
case 'ANNOTATE_ELEMENT': {
const { elementId, text } = payload; // OK - different block scope
// ...
break;
}
case 'SET_WIDGET_STATE': {
const { cameraPosition, scale } = payload;
// ...
break;
}
}
```
**Alternative - Use different variable names:**
```javascript
switch (action) {
case 'HIGHLIGHT_ELEMENT':
const highlightData = payload;
// Use highlightData.elementId
break;
case 'ANNOTATE_ELEMENT':
const annotateData = payload;
// Use annotateData.elementId
break;
}
```
### 2. Teacher Actions Listener Pattern
Always wrap switch cases in braces:
```javascript
window.addEventListener('message', (event) => {
const { action, payload } = event.data;
switch (action) {
case 'SET_WIDGET_STATE': {
if (payload.cameraPosition) camera.position.set(...payload.cameraPosition);
if (payload.scale !== undefined) {
objects.cellGroup.scale.setScalar(payload.scale);
}
break;
}
case 'HIGHLIGHT_ELEMENT': {
const { elementId, highlight } = payload;
if (objects[elementId]) {
objects[elementId].forEach(mesh => {
mesh.material.emissive.set(highlight ? 0xffff00 : 0x000000);
});
}
break;
}
case 'ANNOTATE_ELEMENT': {
const { elementId, text } = payload;
// Create annotation tooltip
break;
}
}
});
```
## Output Format
Return ONLY the HTML document, no markdown fences or explanations.
**CRITICAL: Output EXACTLY ONE HTML document.**
- Do NOT duplicate content
- Do NOT include multiple `<!DOCTYPE html>` tags
- The output must end with exactly one `</html>` tag