初始提交
This commit is contained in:
912
rg-09112127/html/number_people.html
Normal file
912
rg-09112127/html/number_people.html
Normal file
@@ -0,0 +1,912 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>智慧猫头鹰课堂助手</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Microsoft YaHei", sans-serif;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
#fef9e7 0%,
|
||||
#f8f4e6 50%,
|
||||
#e8f5e8 100%
|
||||
);
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||
padding: 40px;
|
||||
margin-top: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #8b4513;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
font-size: 2.2em;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.owl-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.owl-stage {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
border-radius: 20px;
|
||||
border: 3px solid #d2691e;
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
margin-bottom: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
background: linear-gradient(135deg, #f0e68c 0%, #daa520 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.owl-stage:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.owl-stage.speaking {
|
||||
animation: owlGlow 2s infinite;
|
||||
}
|
||||
|
||||
#three-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 17px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@keyframes owlGlow {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(210, 105, 30, 0.7);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 20px rgba(210, 105, 30, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(210, 105, 30, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.speech-bubble {
|
||||
position: relative;
|
||||
background: linear-gradient(135deg, #ff8c42 0%, #ff6b35 100%);
|
||||
color: white;
|
||||
border-radius: 15px;
|
||||
padding: 20px 25px;
|
||||
max-width: 85%;
|
||||
text-align: center;
|
||||
margin-bottom: 25px;
|
||||
opacity: 0;
|
||||
transition: all 0.5s ease;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.speech-bubble:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -12px;
|
||||
border-width: 12px;
|
||||
border-style: solid;
|
||||
border-color: #ff6b35 transparent transparent transparent;
|
||||
}
|
||||
|
||||
.speech-bubble.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 25px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
button {
|
||||
background: linear-gradient(135deg, #cd853f 0%, #8b4513 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 25px;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background: linear-gradient(135deg, #d3d3d3 0%, #a9a9a9 100%);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.status {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
font-size: 18px;
|
||||
color: #8b4513;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
width: 100%;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
padding: 15px;
|
||||
border: 2px solid #daa520;
|
||||
border-radius: 12px;
|
||||
font-family: inherit;
|
||||
font-size: 16px;
|
||||
resize: vertical;
|
||||
transition: border-color 0.3s ease;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: #ff8c42;
|
||||
box-shadow: 0 0 10px rgba(255, 140, 66, 0.3);
|
||||
}
|
||||
|
||||
.features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 35px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: linear-gradient(135deg, #fff8dc 0%, #f5deb3 100%);
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
border: 2px solid #daa520;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
color: #8b4513;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
color: #696969;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.wisdom-quote {
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
color: #8b4513;
|
||||
margin-top: 30px;
|
||||
padding: 15px;
|
||||
background: rgba(218, 165, 32, 0.1);
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #daa520;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🦉 智慧猫头鹰课堂助手</h1>
|
||||
|
||||
<div class="owl-container">
|
||||
<div id="owlStage" class="owl-stage">
|
||||
<div id="three-container"></div>
|
||||
</div>
|
||||
<div id="speechBubble" class="speech-bubble">
|
||||
你好!我是智慧猫头鹰,很高兴成为你的课堂助手!有什么知识想要探索吗?🌟
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-container">
|
||||
<textarea id="speechText" placeholder="输入你希望猫头鹰讲解的内容...">
|
||||
欢迎来到智慧的课堂!我是你的猫头鹰老师。今天我们将一起探索知识的森林,学习新的技能。猫头鹰象征着智慧和洞察力,让我们用敏锐的眼光去发现学习的乐趣吧!</textarea
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button id="speakBtn">🎓 开始讲解</button>
|
||||
<button id="stopBtn" disabled>⏹️ 停止讲解</button>
|
||||
<button id="expressionBtn">😊 切换表情</button>
|
||||
</div>
|
||||
|
||||
<div id="status" class="status">智慧待机中...</div>
|
||||
|
||||
<div class="features">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">📖</div>
|
||||
<h3>知识讲解</h3>
|
||||
<p>用生动有趣的方式为学生讲解各种知识点</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🤔</div>
|
||||
<h3>启发思考</h3>
|
||||
<p>通过提问和引导帮助学生深入思考问题</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🌳</div>
|
||||
<h3>知识树</h3>
|
||||
<p>构建完整的知识体系,让学习更有条理</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">✨</div>
|
||||
<h3>智慧启迪</h3>
|
||||
<p>分享学习方法和人生智慧,启迪心灵</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wisdom-quote">
|
||||
💡 "知识是智慧的翅膀,让我们一起在学习的天空中翱翔!"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const owlStage = document.getElementById("owlStage");
|
||||
const speechBubble = document.getElementById("speechBubble");
|
||||
const speechText = document.getElementById("speechText");
|
||||
const speakBtn = document.getElementById("speakBtn");
|
||||
const stopBtn = document.getElementById("stopBtn");
|
||||
const expressionBtn = document.getElementById("expressionBtn");
|
||||
const status = document.getElementById("status");
|
||||
|
||||
let speechSynthesis = window.speechSynthesis;
|
||||
let currentUtterance = null;
|
||||
|
||||
// Three.js 猫头鹰
|
||||
let scene, camera, renderer, owl;
|
||||
let isSpeaking = false;
|
||||
let animationId;
|
||||
let currentExpression = "normal";
|
||||
|
||||
// 初始化Three.js场景
|
||||
function initThreeJS() {
|
||||
const container = document.getElementById("three-container");
|
||||
|
||||
// 创建场景
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0xf0e68c);
|
||||
|
||||
// 创建相机
|
||||
camera = new THREE.OrthographicCamera(-200, 200, 200, -200, 0.1, 1000);
|
||||
camera.position.z = 100;
|
||||
|
||||
// 创建渲染器
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(400, 400);
|
||||
renderer.setClearColor(0xf0e68c);
|
||||
container.appendChild(renderer.domElement);
|
||||
|
||||
// 创建猫头鹰
|
||||
createOwl();
|
||||
|
||||
// 开始动画循环
|
||||
animate();
|
||||
}
|
||||
|
||||
// 创建猫头鹰角色
|
||||
function createOwl() {
|
||||
owl = new THREE.Group();
|
||||
|
||||
// 树枝
|
||||
const branchGeometry = new THREE.CylinderGeometry(4, 6, 120, 8);
|
||||
const branchMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0x8b4513,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const branch = new THREE.Mesh(branchGeometry, branchMaterial);
|
||||
branch.rotation.z = Math.PI / 2;
|
||||
branch.position.y = -120;
|
||||
owl.add(branch);
|
||||
|
||||
// 叶子
|
||||
const leafPositions = [
|
||||
{ x: -80, y: -110, rotation: 0.3 },
|
||||
{ x: -40, y: -115, rotation: -0.2 },
|
||||
{ x: 20, y: -112, rotation: 0.1 },
|
||||
{ x: 60, y: -118, rotation: -0.4 },
|
||||
{ x: 90, y: -108, rotation: 0.2 },
|
||||
];
|
||||
|
||||
leafPositions.forEach((pos) => {
|
||||
const leafGeometry = new THREE.CircleGeometry(12, 8);
|
||||
const leafMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0x32cd32,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const leaf = new THREE.Mesh(leafGeometry, leafMaterial);
|
||||
leaf.position.set(pos.x, pos.y, 1);
|
||||
leaf.rotation.z = pos.rotation;
|
||||
leaf.scale.set(1, 1.8, 1);
|
||||
owl.add(leaf);
|
||||
});
|
||||
|
||||
// 身体 - 椭圆形
|
||||
const bodyGeometry = new THREE.CircleGeometry(80, 32);
|
||||
const bodyMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xd2691e,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
|
||||
body.position.y = -40;
|
||||
body.scale.set(1, 1.3, 1);
|
||||
owl.add(body);
|
||||
|
||||
// 肚子 - 浅色
|
||||
const bellyGeometry = new THREE.CircleGeometry(55, 32);
|
||||
const bellyMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xfff8dc,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const belly = new THREE.Mesh(bellyGeometry, bellyMaterial);
|
||||
belly.position.set(0, -45, 1);
|
||||
belly.scale.set(1, 1.2, 1);
|
||||
owl.add(belly);
|
||||
|
||||
// 羽毛纹理 - 身体上的小圆点
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const featherGeometry = new THREE.CircleGeometry(3, 8);
|
||||
const featherMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xcd853f,
|
||||
side: THREE.DoubleSide,
|
||||
transparent: true,
|
||||
opacity: 0.6,
|
||||
});
|
||||
const feather = new THREE.Mesh(featherGeometry, featherMaterial);
|
||||
feather.position.set(
|
||||
(Math.random() - 0.5) * 80,
|
||||
-60 + (Math.random() - 0.5) * 40,
|
||||
2
|
||||
);
|
||||
owl.add(feather);
|
||||
}
|
||||
|
||||
// 头部 - 圆形
|
||||
const headGeometry = new THREE.CircleGeometry(70, 32);
|
||||
const headMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xd2691e,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const head = new THREE.Mesh(headGeometry, headMaterial);
|
||||
head.position.y = 30;
|
||||
owl.add(head);
|
||||
|
||||
// 脸部 - 浅色心形区域
|
||||
const faceGeometry = new THREE.CircleGeometry(50, 32);
|
||||
const faceMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xfff8dc,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const face = new THREE.Mesh(faceGeometry, faceMaterial);
|
||||
face.position.set(0, 25, 1);
|
||||
face.scale.set(1, 1.1, 1);
|
||||
owl.add(face);
|
||||
|
||||
// 眼睛外圈 - 大圆
|
||||
const eyeOuterGeometry = new THREE.CircleGeometry(35, 32);
|
||||
const eyeOuterMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xffffff,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
|
||||
const leftEyeOuter = new THREE.Mesh(eyeOuterGeometry, eyeOuterMaterial);
|
||||
leftEyeOuter.position.set(-25, 40, 2);
|
||||
owl.add(leftEyeOuter);
|
||||
|
||||
const rightEyeOuter = new THREE.Mesh(
|
||||
eyeOuterGeometry,
|
||||
eyeOuterMaterial
|
||||
);
|
||||
rightEyeOuter.position.set(25, 40, 2);
|
||||
owl.add(rightEyeOuter);
|
||||
|
||||
// 眼睛内圈
|
||||
const eyeInnerGeometry = new THREE.CircleGeometry(28, 32);
|
||||
const eyeInnerMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0x000000,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
|
||||
const leftEyeInner = new THREE.Mesh(eyeInnerGeometry, eyeInnerMaterial);
|
||||
leftEyeInner.position.set(-25, 40, 3);
|
||||
owl.add(leftEyeInner);
|
||||
|
||||
const rightEyeInner = new THREE.Mesh(
|
||||
eyeInnerGeometry,
|
||||
eyeInnerMaterial
|
||||
);
|
||||
rightEyeInner.position.set(25, 40, 3);
|
||||
owl.add(rightEyeInner);
|
||||
|
||||
// 瞳孔
|
||||
const pupilGeometry = new THREE.CircleGeometry(20, 16);
|
||||
const pupilMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0x8b4513,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
|
||||
const leftPupil = new THREE.Mesh(pupilGeometry, pupilMaterial);
|
||||
leftPupil.position.set(-25, 40, 4);
|
||||
owl.add(leftPupil);
|
||||
|
||||
const rightPupil = new THREE.Mesh(pupilGeometry, pupilMaterial);
|
||||
rightPupil.position.set(25, 40, 4);
|
||||
owl.add(rightPupil);
|
||||
|
||||
// 眼睛高光
|
||||
const highlightGeometry = new THREE.CircleGeometry(8, 8);
|
||||
const highlightMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xffffff,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
|
||||
const leftHighlight = new THREE.Mesh(
|
||||
highlightGeometry,
|
||||
highlightMaterial
|
||||
);
|
||||
leftHighlight.position.set(-20, 45, 5);
|
||||
owl.add(leftHighlight);
|
||||
|
||||
const rightHighlight = new THREE.Mesh(
|
||||
highlightGeometry,
|
||||
highlightMaterial
|
||||
);
|
||||
rightHighlight.position.set(20, 45, 5);
|
||||
owl.add(rightHighlight);
|
||||
|
||||
// 小高光
|
||||
const smallHighlightGeometry = new THREE.CircleGeometry(4, 8);
|
||||
const leftSmallHighlight = new THREE.Mesh(
|
||||
smallHighlightGeometry,
|
||||
highlightMaterial
|
||||
);
|
||||
leftSmallHighlight.position.set(-30, 35, 5);
|
||||
owl.add(leftSmallHighlight);
|
||||
|
||||
const rightSmallHighlight = new THREE.Mesh(
|
||||
smallHighlightGeometry,
|
||||
highlightMaterial
|
||||
);
|
||||
rightSmallHighlight.position.set(30, 35, 5);
|
||||
owl.add(rightSmallHighlight);
|
||||
|
||||
// 眉毛/头部羽毛装饰
|
||||
const eyebrowGeometry = new THREE.CircleGeometry(15, 8);
|
||||
const eyebrowMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xcd853f,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
|
||||
const leftEyebrow = new THREE.Mesh(eyebrowGeometry, eyebrowMaterial);
|
||||
leftEyebrow.position.set(-30, 65, 2);
|
||||
leftEyebrow.scale.set(1.5, 0.6, 1);
|
||||
leftEyebrow.rotation.z = 0.3;
|
||||
owl.add(leftEyebrow);
|
||||
|
||||
const rightEyebrow = new THREE.Mesh(eyebrowGeometry, eyebrowMaterial);
|
||||
rightEyebrow.position.set(30, 65, 2);
|
||||
rightEyebrow.scale.set(1.5, 0.6, 1);
|
||||
rightEyebrow.rotation.z = -0.3;
|
||||
owl.add(rightEyebrow);
|
||||
|
||||
// 喙
|
||||
const beakGeometry = new THREE.CircleGeometry(8, 8);
|
||||
const beakMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xff8c00,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const beak = new THREE.Mesh(beakGeometry, beakMaterial);
|
||||
beak.position.set(0, 15, 3);
|
||||
beak.scale.set(1, 0.8, 1);
|
||||
owl.add(beak);
|
||||
|
||||
// 翅膀
|
||||
const wingGeometry = new THREE.CircleGeometry(45, 16);
|
||||
const wingMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xcd853f,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
|
||||
const leftWing = new THREE.Mesh(wingGeometry, wingMaterial);
|
||||
leftWing.position.set(-85, -30, 0);
|
||||
leftWing.scale.set(0.8, 1.4, 1);
|
||||
leftWing.rotation.z = 0.3;
|
||||
owl.add(leftWing);
|
||||
|
||||
const rightWing = new THREE.Mesh(wingGeometry, wingMaterial);
|
||||
rightWing.position.set(85, -30, 0);
|
||||
rightWing.scale.set(0.8, 1.4, 1);
|
||||
rightWing.rotation.z = -0.3;
|
||||
owl.add(rightWing);
|
||||
|
||||
// 翅膀羽毛纹理
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const wingFeatherGeometry = new THREE.CircleGeometry(4, 8);
|
||||
const wingFeatherMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xd2691e,
|
||||
side: THREE.DoubleSide,
|
||||
transparent: true,
|
||||
opacity: 0.7,
|
||||
});
|
||||
|
||||
const leftWingFeather = new THREE.Mesh(
|
||||
wingFeatherGeometry,
|
||||
wingFeatherMaterial
|
||||
);
|
||||
leftWingFeather.position.set(
|
||||
-85 + (Math.random() - 0.5) * 40,
|
||||
-30 + (Math.random() - 0.5) * 60,
|
||||
1
|
||||
);
|
||||
owl.add(leftWingFeather);
|
||||
|
||||
const rightWingFeather = new THREE.Mesh(
|
||||
wingFeatherGeometry,
|
||||
wingFeatherMaterial
|
||||
);
|
||||
rightWingFeather.position.set(
|
||||
85 + (Math.random() - 0.5) * 40,
|
||||
-30 + (Math.random() - 0.5) * 60,
|
||||
1
|
||||
);
|
||||
owl.add(rightWingFeather);
|
||||
}
|
||||
|
||||
// 爪子
|
||||
const clawGeometry = new THREE.CircleGeometry(6, 8);
|
||||
const clawMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xff8c00,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
|
||||
// 左爪
|
||||
const leftClaw1 = new THREE.Mesh(clawGeometry, clawMaterial);
|
||||
leftClaw1.position.set(-25, -110, 2);
|
||||
leftClaw1.scale.set(0.6, 1.5, 1);
|
||||
owl.add(leftClaw1);
|
||||
|
||||
const leftClaw2 = new THREE.Mesh(clawGeometry, clawMaterial);
|
||||
leftClaw2.position.set(-15, -112, 2);
|
||||
leftClaw2.scale.set(0.6, 1.5, 1);
|
||||
owl.add(leftClaw2);
|
||||
|
||||
// 右爪
|
||||
const rightClaw1 = new THREE.Mesh(clawGeometry, clawMaterial);
|
||||
rightClaw1.position.set(15, -112, 2);
|
||||
rightClaw1.scale.set(0.6, 1.5, 1);
|
||||
owl.add(rightClaw1);
|
||||
|
||||
const rightClaw2 = new THREE.Mesh(clawGeometry, clawMaterial);
|
||||
rightClaw2.position.set(25, -110, 2);
|
||||
rightClaw2.scale.set(0.6, 1.5, 1);
|
||||
owl.add(rightClaw2);
|
||||
|
||||
// 存储可动画的部分
|
||||
owl.userData = {
|
||||
head: head,
|
||||
body: body,
|
||||
leftEyeOuter: leftEyeOuter,
|
||||
rightEyeOuter: rightEyeOuter,
|
||||
leftEyeInner: leftEyeInner,
|
||||
rightEyeInner: rightEyeInner,
|
||||
leftPupil: leftPupil,
|
||||
rightPupil: rightPupil,
|
||||
leftHighlight: leftHighlight,
|
||||
rightHighlight: rightHighlight,
|
||||
leftSmallHighlight: leftSmallHighlight,
|
||||
rightSmallHighlight: rightSmallHighlight,
|
||||
beak: beak,
|
||||
leftWing: leftWing,
|
||||
rightWing: rightWing,
|
||||
blinkTimer: 0,
|
||||
speakTimer: 0,
|
||||
idleTimer: 0,
|
||||
};
|
||||
|
||||
scene.add(owl);
|
||||
}
|
||||
|
||||
// 动画循环
|
||||
function animate() {
|
||||
animationId = requestAnimationFrame(animate);
|
||||
|
||||
if (owl && owl.userData) {
|
||||
const userData = owl.userData;
|
||||
|
||||
// 眨眼动画
|
||||
userData.blinkTimer += 0.016;
|
||||
if (userData.blinkTimer > 4) {
|
||||
userData.leftEyeOuter.scale.y = 0.1;
|
||||
userData.rightEyeOuter.scale.y = 0.1;
|
||||
userData.leftEyeInner.scale.y = 0.1;
|
||||
userData.rightEyeInner.scale.y = 0.1;
|
||||
if (userData.blinkTimer > 4.2) {
|
||||
userData.leftEyeOuter.scale.y = 1;
|
||||
userData.rightEyeOuter.scale.y = 1;
|
||||
userData.leftEyeInner.scale.y = 1;
|
||||
userData.rightEyeInner.scale.y = 1;
|
||||
userData.blinkTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 说话动画
|
||||
if (isSpeaking) {
|
||||
userData.speakTimer += 0.2;
|
||||
userData.beak.scale.y = 0.8 + Math.sin(userData.speakTimer) * 0.3;
|
||||
userData.head.position.y =
|
||||
30 + Math.sin(userData.speakTimer * 0.7) * 2;
|
||||
|
||||
// 说话时翅膀轻微扇动
|
||||
userData.leftWing.rotation.z =
|
||||
0.3 + Math.sin(userData.speakTimer * 0.5) * 0.1;
|
||||
userData.rightWing.rotation.z =
|
||||
-0.3 - Math.sin(userData.speakTimer * 0.5) * 0.1;
|
||||
} else {
|
||||
userData.beak.scale.y = 0.8;
|
||||
userData.head.position.y = 30;
|
||||
userData.leftWing.rotation.z = 0.3;
|
||||
userData.rightWing.rotation.z = -0.3;
|
||||
}
|
||||
|
||||
// 闲置动画 - 轻微摆动
|
||||
userData.idleTimer += 0.008;
|
||||
owl.rotation.z = Math.sin(userData.idleTimer) * 0.02;
|
||||
|
||||
// 头部轻微转动
|
||||
userData.head.rotation.z = Math.sin(userData.idleTimer * 0.7) * 0.05;
|
||||
}
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
// 开始说话动画
|
||||
function startSpeaking() {
|
||||
isSpeaking = true;
|
||||
owlStage.classList.add("speaking");
|
||||
}
|
||||
|
||||
// 停止说话动画
|
||||
function stopSpeaking() {
|
||||
isSpeaking = false;
|
||||
owlStage.classList.remove("speaking");
|
||||
}
|
||||
|
||||
// 切换表情
|
||||
function changeExpression() {
|
||||
if (!owl || !owl.userData) return;
|
||||
|
||||
const expressions = ["normal", "happy", "thinking", "surprised"];
|
||||
const currentIndex = expressions.indexOf(currentExpression);
|
||||
currentExpression =
|
||||
expressions[(currentIndex + 1) % expressions.length];
|
||||
|
||||
const userData = owl.userData;
|
||||
|
||||
switch (currentExpression) {
|
||||
case "happy":
|
||||
// 开心表情 - 眯眼
|
||||
userData.leftEyeOuter.scale.y = 0.7;
|
||||
userData.rightEyeOuter.scale.y = 0.7;
|
||||
userData.beak.scale.set(1.2, 1, 1);
|
||||
status.textContent = "😊 开心模式";
|
||||
break;
|
||||
case "thinking":
|
||||
// 思考表情 - 头部倾斜
|
||||
userData.head.rotation.z = 0.2;
|
||||
userData.leftPupil.position.x = -20;
|
||||
userData.rightPupil.position.x = 30;
|
||||
status.textContent = "🤔 思考模式";
|
||||
break;
|
||||
case "surprised":
|
||||
// 惊讶表情 - 眼睛放大
|
||||
userData.leftEyeOuter.scale.set(1.3, 1.3, 1);
|
||||
userData.rightEyeOuter.scale.set(1.3, 1.3, 1);
|
||||
userData.beak.scale.set(0.8, 1.2, 1);
|
||||
status.textContent = "😮 惊讶模式";
|
||||
break;
|
||||
default: // normal
|
||||
// 恢复正常
|
||||
userData.leftEyeOuter.scale.set(1, 1, 1);
|
||||
userData.rightEyeOuter.scale.set(1, 1, 1);
|
||||
userData.leftEyeInner.scale.set(1, 1, 1);
|
||||
userData.rightEyeInner.scale.set(1, 1, 1);
|
||||
userData.head.rotation.z = 0;
|
||||
userData.leftPupil.position.x = -25;
|
||||
userData.rightPupil.position.x = 25;
|
||||
userData.beak.scale.set(1, 0.8, 1);
|
||||
status.textContent = "😐 正常模式";
|
||||
}
|
||||
}
|
||||
|
||||
// 初始显示欢迎消息
|
||||
setTimeout(() => {
|
||||
speechBubble.classList.add("visible");
|
||||
}, 800);
|
||||
|
||||
// 事件监听器
|
||||
speakBtn.addEventListener("click", () => {
|
||||
const text = speechText.value.trim();
|
||||
if (!text) {
|
||||
status.textContent = "请输入要讲解的内容";
|
||||
return;
|
||||
}
|
||||
|
||||
if (speechSynthesis.speaking) {
|
||||
speechSynthesis.cancel();
|
||||
}
|
||||
|
||||
currentUtterance = new SpeechSynthesisUtterance(text);
|
||||
currentUtterance.lang = "zh-CN";
|
||||
currentUtterance.rate = 0.9;
|
||||
currentUtterance.pitch = 1.1;
|
||||
|
||||
currentUtterance.onstart = () => {
|
||||
startSpeaking();
|
||||
speechBubble.textContent = text;
|
||||
speechBubble.classList.add("visible");
|
||||
status.textContent = "📚 正在智慧讲解中...";
|
||||
speakBtn.disabled = true;
|
||||
stopBtn.disabled = false;
|
||||
};
|
||||
|
||||
currentUtterance.onend = () => {
|
||||
stopSpeaking();
|
||||
status.textContent = "✨ 讲解完成!";
|
||||
speakBtn.disabled = false;
|
||||
stopBtn.disabled = true;
|
||||
};
|
||||
|
||||
currentUtterance.onerror = (event) => {
|
||||
stopSpeaking();
|
||||
status.textContent = `❌ 讲解出错: ${event.error}`;
|
||||
speakBtn.disabled = false;
|
||||
stopBtn.disabled = true;
|
||||
};
|
||||
|
||||
speechSynthesis.speak(currentUtterance);
|
||||
});
|
||||
|
||||
stopBtn.addEventListener("click", () => {
|
||||
if (speechSynthesis.speaking) {
|
||||
speechSynthesis.cancel();
|
||||
stopSpeaking();
|
||||
status.textContent = "⏹️ 讲解已停止";
|
||||
speakBtn.disabled = false;
|
||||
stopBtn.disabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
expressionBtn.addEventListener("click", changeExpression);
|
||||
|
||||
// 鼠标交互
|
||||
function addMouseInteractions() {
|
||||
const container = document.getElementById("three-container");
|
||||
|
||||
container.addEventListener("mouseenter", () => {
|
||||
if (owl && owl.userData) {
|
||||
container.addEventListener("mousemove", followMouse);
|
||||
}
|
||||
});
|
||||
|
||||
container.addEventListener("mouseleave", () => {
|
||||
if (owl && owl.userData) {
|
||||
container.removeEventListener("mousemove", followMouse);
|
||||
resetEyes();
|
||||
}
|
||||
});
|
||||
|
||||
container.addEventListener("click", () => {
|
||||
if (owl && owl.userData) {
|
||||
blink();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 眼睛跟随鼠标
|
||||
function followMouse(event) {
|
||||
if (!owl || !owl.userData) return;
|
||||
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
const x = (event.clientX - rect.left - 200) / 200;
|
||||
const y = (event.clientY - rect.top - 200) / 200;
|
||||
|
||||
const userData = owl.userData;
|
||||
const maxOffset = 6;
|
||||
|
||||
userData.leftPupil.position.x = -25 + x * maxOffset;
|
||||
userData.leftPupil.position.y = 40 - y * maxOffset;
|
||||
userData.rightPupil.position.x = 25 + x * maxOffset;
|
||||
userData.rightPupil.position.y = 40 - y * maxOffset;
|
||||
|
||||
userData.leftHighlight.position.x = -20 + x * maxOffset * 0.8;
|
||||
userData.leftHighlight.position.y = 45 - y * maxOffset * 0.8;
|
||||
userData.rightHighlight.position.x = 20 + x * maxOffset * 0.8;
|
||||
userData.rightHighlight.position.y = 45 - y * maxOffset * 0.8;
|
||||
}
|
||||
|
||||
// 重置眼睛
|
||||
function resetEyes() {
|
||||
if (!owl || !owl.userData) return;
|
||||
|
||||
const userData = owl.userData;
|
||||
userData.leftPupil.position.set(-25, 40, 4);
|
||||
userData.rightPupil.position.set(25, 40, 4);
|
||||
userData.leftHighlight.position.set(-20, 45, 5);
|
||||
userData.rightHighlight.position.set(20, 45, 5);
|
||||
}
|
||||
|
||||
// 眨眼
|
||||
function blink() {
|
||||
if (!owl || !owl.userData) return;
|
||||
|
||||
const userData = owl.userData;
|
||||
userData.leftEyeOuter.scale.y = 0.1;
|
||||
userData.rightEyeOuter.scale.y = 0.1;
|
||||
userData.leftEyeInner.scale.y = 0.1;
|
||||
userData.rightEyeInner.scale.y = 0.1;
|
||||
|
||||
setTimeout(() => {
|
||||
userData.leftEyeOuter.scale.y = 1;
|
||||
userData.rightEyeOuter.scale.y = 1;
|
||||
userData.leftEyeInner.scale.y = 1;
|
||||
userData.rightEyeInner.scale.y = 1;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// 初始化
|
||||
window.addEventListener("load", () => {
|
||||
initThreeJS();
|
||||
addMouseInteractions();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user