ThreeJS骨骼示例

<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>骨骼动画混合演示</title><style>body {margin: 0;padding: 0;background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);font-family: 'Arial', sans-serif;overflow: hidden;}#container {position: relative;width: 100vw;height: 100vh;}#controls {position: absolute;top: 20px;right: 20px;background: rgba(0, 0, 0, 0.8);padding: 20px;border-radius: 10px;backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.2);z-index: 100;min-width: 200px;}.control-section {margin-bottom: 15px;}.control-section h3 {color: #fff;margin: 0 0 10px 0;font-size: 14px;text-transform: uppercase;letter-spacing: 1px;}button {background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);border: none;padding: 8px 15px;margin: 3px;border-radius: 5px;color: white;cursor: pointer;font-size: 12px;transition: all 0.3s ease;min-width: 80px;}button:hover {transform: translateY(-2px);box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);}button.active {background: linear-gradient(45deg, #f093fb 0%, #f5576c 100%);box-shadow: 0 0 20px rgba(240, 147, 251, 0.5);}.slider-container {margin: 10px 0;}.slider-container label {color: #fff;font-size: 12px;display: block;margin-bottom: 5px;}input[type="range"] {width: 100%;height: 4px;border-radius: 2px;background: rgba(255, 255, 255, 0.3);outline: none;-webkit-appearance: none;}input[type="range"]::-webkit-slider-thumb {appearance: none;width: 16px;height: 16px;border-radius: 50%;background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);cursor: pointer;}#info {position: absolute;bottom: 20px;left: 20px;color: rgba(255, 255, 255, 0.8);font-size: 12px;max-width: 300px;}</style>
<input type="hidden" id="_o_dbjbempljhcmhlfpfacalomonjpalpko" data-inspect-config="7"><script type="text/javascript" src="chrome-extension://odphnbhiddhdpoccbialllejaajemdio/scripts/inspector.js"></script><link id="fl-highlights-css" rel="stylesheet" type="text/css" href="chrome-extension://dppcnmibpgmagiigcnfdjpnghplibbna/fl-highlights.css"><style id="monica-reading-highlight-style">.monica-reading-highlight {animation: fadeInOut 1.5s ease-in-out;}@keyframes fadeInOut {0%, 100% { background-color: transparent; }30%, 70% { background-color: rgba(2, 118, 255, 0.20); }}</style><style data-id="immersive-translate-input-injected-css">.immersive-translate-input {position: absolute;top: 0;right: 0;left: 0;bottom: 0;z-index: 2147483647;display: flex;justify-content: center;align-items: center;
}
.immersive-translate-attach-loading::after {content: " ";--loading-color: #f78fb6;width: 6px;height: 6px;border-radius: 50%;display: block;margin: 12px auto;position: relative;color: white;left: -100px;box-sizing: border-box;animation: immersiveTranslateShadowRolling 1.5s linear infinite;position: absolute;top: 50%;left: 50%;transform: translate(-2000%, -50%);z-index: 100;
}.immersive-translate-loading-spinner {vertical-align: middle !important;width: 10px !important;height: 10px !important;display: inline-block !important;margin: 0 4px !important;border: 2px rgba(221, 244, 255, 0.6) solid !important;border-top: 2px rgba(0, 0, 0, 0.375) solid !important;border-left: 2px rgba(0, 0, 0, 0.375) solid !important;border-radius: 50% !important;padding: 0 !important;-webkit-animation: immersive-translate-loading-animation 0.6s infinite linear !important;animation: immersive-translate-loading-animation 0.6s infinite linear !important;
}@-webkit-keyframes immersive-translate-loading-animation {from {-webkit-transform: rotate(0deg);}to {-webkit-transform: rotate(359deg);}
}@keyframes immersive-translate-loading-animation {from {transform: rotate(0deg);}to {transform: rotate(359deg);}
}.immersive-translate-input-loading {--loading-color: #f78fb6;width: 6px;height: 6px;border-radius: 50%;display: block;margin: 12px auto;position: relative;color: white;left: -100px;box-sizing: border-box;animation: immersiveTranslateShadowRolling 1.5s linear infinite;
}@keyframes immersiveTranslateShadowRolling {0% {box-shadow: 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0),0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0);}12% {box-shadow: 100px 0 var(--loading-color), 0px 0 rgba(255, 255, 255, 0),0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0);}25% {box-shadow: 110px 0 var(--loading-color), 100px 0 var(--loading-color),0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0);}36% {box-shadow: 120px 0 var(--loading-color), 110px 0 var(--loading-color),100px 0 var(--loading-color), 0px 0 rgba(255, 255, 255, 0);}50% {box-shadow: 130px 0 var(--loading-color), 120px 0 var(--loading-color),110px 0 var(--loading-color), 100px 0 var(--loading-color);}62% {box-shadow: 200px 0 rgba(255, 255, 255, 0), 130px 0 var(--loading-color),120px 0 var(--loading-color), 110px 0 var(--loading-color);}75% {box-shadow: 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0),130px 0 var(--loading-color), 120px 0 var(--loading-color);}87% {box-shadow: 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0),200px 0 rgba(255, 255, 255, 0), 130px 0 var(--loading-color);}100% {box-shadow: 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0),200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0);}
}.immersive-translate-toast {display: flex;position: fixed;z-index: 2147483647;left: 0;right: 0;top: 1%;width: fit-content;padding: 12px 20px;margin: auto;overflow: auto;background: #fef6f9;box-shadow: 0px 4px 10px 0px rgba(0, 10, 30, 0.06);font-size: 15px;border-radius: 8px;color: #333;
}.immersive-translate-toast-content {display: flex;flex-direction: row;align-items: center;
}.immersive-translate-toast-hidden {margin: 0 20px 0 72px;text-decoration: underline;cursor: pointer;
}.immersive-translate-toast-close {color: #666666;font-size: 20px;font-weight: bold;padding: 0 10px;cursor: pointer;
}@media screen and (max-width: 768px) {.immersive-translate-toast {top: 0;padding: 12px 0px 0 10px;}.immersive-translate-toast-content {flex-direction: column;text-align: center;}.immersive-translate-toast-hidden {margin: 10px auto;}
}.immersive-translate-dialog {position: fixed;z-index: 2147483647;left: 0;top: 0;display: flex;width: 300px;flex-direction: column;align-items: center;font-size: 15px;left: 0;right: 0;top: 0;bottom: 0;margin: auto;height: fit-content;border-radius: 20px;background-color: #fff;
}.immersive-translate-modal {display: none;position: fixed;z-index: 2147483647;left: 0;top: 0;width: 100%;height: 100%;overflow: auto;background-color: rgb(0, 0, 0);background-color: rgba(0, 0, 0, 0.4);font-size: 15px;
}.immersive-translate-modal-content {background-color: #fefefe;margin: 10% auto;padding: 40px 24px 24px;border-radius: 12px;width: 350px;font-family: system-ui, -apple-system, "Segoe UI", "Roboto", "Ubuntu","Cantarell", "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji","Segoe UI Symbol", "Noto Color Emoji";position: relative;
}@media screen and (max-width: 768px) {.immersive-translate-modal-content {margin: 25% auto !important;}
}@media screen and (max-width: 480px) {.immersive-translate-modal-content {width: 80vw !important;margin: 20vh auto !important;padding: 20px 12px 12px !important;}.immersive-translate-modal-title {font-size: 14px !important;}.immersive-translate-modal-body {font-size: 13px !important;max-height: 60vh !important;}.immersive-translate-btn {font-size: 13px !important;padding: 8px 16px !important;margin: 0 4px !important;}.immersive-translate-modal-footer {gap: 6px !important;margin-top: 16px !important;}
}.immersive-translate-modal .immersive-translate-modal-content-in-input {max-width: 500px;
}
.immersive-translate-modal-content-in-input .immersive-translate-modal-body {text-align: left;max-height: unset;
}.immersive-translate-modal-title {text-align: center;font-size: 16px;font-weight: 700;color: #333333;
}.immersive-translate-modal-body {text-align: center;font-size: 14px;font-weight: 400;color: #333333;margin-top: 24px;
}@media screen and (max-width: 768px) {.immersive-translate-modal-body {max-height: 250px;overflow-y: auto;}
}.immersive-translate-close {color: #666666;position: absolute;right: 16px;top: 16px;font-size: 20px;font-weight: bold;
}.immersive-translate-close:hover,
.immersive-translate-close:focus {text-decoration: none;cursor: pointer;
}.immersive-translate-modal-footer {display: flex;justify-content: center;flex-wrap: wrap;margin-top: 24px;
}.immersive-translate-btn {width: fit-content;color: #fff;background-color: #ea4c89;border: none;font-size: 14px;margin: 0 8px;padding: 9px 30px;border-radius: 5px;display: flex;align-items: center;justify-content: center;cursor: pointer;transition: background-color 0.3s ease;
}.immersive-translate-btn-container {display: flex;flex-direction: column;align-items: center;justify-content: center;gap: 8px;
}.immersive-translate-btn:hover {background-color: #f082ac;
}
.immersive-translate-btn:disabled {opacity: 0.6;cursor: not-allowed;
}
.immersive-translate-btn:disabled:hover {background-color: #ea4c89;
}.immersive-translate-link-btn {background-color: transparent;color: #ea4c89;border: none;cursor: pointer;height: 30px;line-height: 30px;
}.immersive-translate-cancel-btn {/* gray color */background-color: rgb(89, 107, 120);
}.immersive-translate-cancel-btn:hover {background-color: hsl(205, 20%, 32%);
}.immersive-translate-action-btn {background-color: transparent;color: #ea4c89;border: 1px solid #ea4c89;
}.immersive-translate-btn svg {margin-right: 5px;
}.immersive-translate-link {cursor: pointer;user-select: none;-webkit-user-drag: none;text-decoration: none;color: #ea4c89;-webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
}.immersive-translate-primary-link {cursor: pointer;user-select: none;-webkit-user-drag: none;text-decoration: none;color: #ea4c89;-webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
}.immersive-translate-modal input[type="radio"] {margin: 0 6px;cursor: pointer;
}.immersive-translate-modal label {cursor: pointer;
}.immersive-translate-close-action {position: absolute;top: 2px;right: 0px;cursor: pointer;
}.imt-image-status {background-color: rgba(0, 0, 0, 0.5) !important;display: flex !important;flex-direction: column !important;align-items: center !important;justify-content: center !important;border-radius: 16px !important;
}
.imt-image-status img,
.imt-image-status svg,
.imt-img-loading {width: 28px !important;height: 28px !important;margin: 0 0 8px 0 !important;min-height: 28px !important;min-width: 28px !important;position: relative !important;
}
.imt-img-loading {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAMAAACfWMssAAAAtFBMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////oK74hAAAAPHRSTlMABBMIDyQXHwyBfFdDMSw+OjXCb+5RG51IvV/k0rOqlGRM6KKMhdvNyZBz9MaupmxpWyj437iYd/yJVNZeuUC7AAACt0lEQVRIx53T2XKiUBCA4QYOiyCbiAsuuGBcYtxiYtT3f6/pbqoYHVFO5r+iivpo6DpAWYpqeoFfr9f90DsYAuRSWkFnPO50OgR9PwiCUFcl2GEcx+N/YBh6pvKaefHlUgZd1zVe0NbYcQjGBfzrPE8Xz8aF+71D8gG6DHFPpc4a7xFiCDuhaWgKgGIJQ3d5IMGDrpS4S5KgpIm+en9f6PlAhKby4JwEIxlYJV9h5k5nee9GoxHJ2IDSNB0dwdad1NAxDJ/uXDHYmebdk4PdbkS58CIVHdYSUHTYYRWOJblWSyu2lmy3KNFVJNBhxcuGW4YBVCbYGRZwIooipHsNqjM4FbgOQqQqSKQQU9V8xmi1QlgHqQQ6DDBvRUVCDirs+EzGDGOQTCATgtYTnbCVLgsVgRE0T1QE0qHCFAht2z6dLvJQs3Lo2FQoDxWNUiBhaP4eRgwNkI+dAjVOA/kUrIDwf3CG8NfNOE0eiFotSuo+rBiq8tD9oY4Qzc6YJw99hl1wzpQvD7ef2M8QgnOGJfJw+EltQc+oX2yn907QB22WZcvlUpd143dqQu+8pCJZuGE4xCuPXJqqcs5sNpsI93Rmzym1k4Npk+oD1SH3/a3LOK/JpUBpWfqNySxWzCfNCUITuDG5dtuphrUJ1myeIE9bIsPiKrfqTai5WZxbhtNphYx6GEIHihyGFTI69lje/rxajdh0s0msZ0zYxyPLhYCb1CyHm9Qsd2H37Y3lugVwL9kNh8Ot8cha6fUNQ8nuXi5z9/ExsAO4zQrb/ev1yrCB7lGyQzgYDGuxq1toDN/JGvN+HyWNHKB7zEoK+PX11e12G431erGYzwmytAWU56fkMHY5JJnDRR2eZji3AwtIcrEV8Cojat/BdQ7XOwGV1e1hDjGGjXbdArm8uJZtCH5MbcctVX8A1WpqumJHwckAAAAASUVORK5CYII=");background-size: 28px 28px;animation: image-loading-rotate 1s linear infinite !important;
}.imt-image-status span {color: var(--bg-2, #fff) !important;font-size: 14px !important;line-height: 14px !important;font-weight: 500 !important;font-family: "PingFang SC", Arial, sans-serif !important;
}.imt-primary-button {display: flex;padding: 12px 80px;justify-content: center;align-items: center;gap: 8px;border-radius: 8px;background: #ea4c89;color: #fff;font-size: 16px;font-style: normal;font-weight: 700;line-height: 24px;border: none;cursor: pointer;
}.imt-retry-text {color:  #999;text-align: center;font-size: 14px;font-style: normal;font-weight: 400;line-height: 21px;cursor: pointer;
}.imt-action-container {display: flex;flex-direction: column;gap: 12px;
}.imt-modal-content-text {text-align: left;color:  #333;font-size: 16px;font-weight: 400;line-height: 24px;
}@keyframes image-loading-rotate {from {transform: rotate(360deg);}to {transform: rotate(0deg);}
}
</style></head>
<body monica-id="ofpnmcalabcbjgholdjcjblkibolbppb" monica-version="7.9.7"><div id="container"><div id="controls"><div class="control-section"><h3>动作切换</h3><button id="idle" class="active">站立</button><button id="walk">走路</button><button id="run">奔跑</button><button id="jump">跳跃</button></div><div class="control-section"><h3>动画设置</h3><div class="slider-container"><label>混合速度: <span id="blendSpeedValue">2.0</span></label><input type="range" id="blendSpeed" min="0.5" max="5.0" step="0.1" value="2.0"></div><div class="slider-container"><label>动画速度: <span id="animSpeedValue">1.0</span></label><input type="range" id="animSpeed" min="0.1" max="3.0" step="0.1" value="1.0"></div></div><div class="control-section"><button id="autoMode">自动模式</button></div></div><div id="info"><strong>骨骼动画混合演示</strong><br>点击右侧按钮切换动作,观察不同动画之间的平滑过渡。<br>• 蓝色关节:骨骼系统<br>• 绿色线条:骨骼连接<br>• 红色方块:蒙皮网格</div><canvas width="753" height="672" style="display: block; width: 753px; height: 672px;"></canvas></div><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script><script>// 全局变量let scene, camera, renderer, character;let currentAction = 'idle';let isAutoMode = false;let autoModeTimer = 0;let blendSpeed = 2.0;let animationSpeed = 1.0;// 动画状态const animations = {idle: { weight: 1.0, speed: 1.0 },walk: { weight: 0.0, speed: 1.5 },run: { weight: 0.0, speed: 2.0 },jump: { weight: 0.0, speed: 1.2 }};// 简化的骨骼系统class SimpleSkeleton {constructor() {this.bones = [];this.joints = [];this.meshes = [];this.time = 0;this.createSkeleton();}createSkeleton() {// 创建骨骼结构(简化的人形)const boneStructure = [{ name: 'root', pos: [0, 0, 0], parent: null },{ name: 'spine', pos: [0, 1, 0], parent: 'root' },{ name: 'chest', pos: [0, 2, 0], parent: 'spine' },{ name: 'neck', pos: [0, 2.8, 0], parent: 'chest' },{ name: 'head', pos: [0, 3.5, 0], parent: 'neck' },{ name: 'leftShoulder', pos: [-0.8, 2.5, 0], parent: 'chest' },{ name: 'leftArm', pos: [-1.5, 2.5, 0], parent: 'leftShoulder' },{ name: 'leftForearm', pos: [-2.2, 2.5, 0], parent: 'leftArm' },{ name: 'rightShoulder', pos: [0.8, 2.5, 0], parent: 'chest' },{ name: 'rightArm', pos: [1.5, 2.5, 0], parent: 'rightShoulder' },{ name: 'rightForearm', pos: [2.2, 2.5, 0], parent: 'rightArm' },{ name: 'leftHip', pos: [-0.3, 0, 0], parent: 'root' },{ name: 'leftThigh', pos: [-0.3, -0.8, 0], parent: 'leftHip' },{ name: 'leftCalf', pos: [-0.3, -1.6, 0], parent: 'leftThigh' },{ name: 'leftFoot', pos: [-0.3, -2.2, 0.2], parent: 'leftCalf' },{ name: 'rightHip', pos: [0.3, 0, 0], parent: 'root' },{ name: 'rightThigh', pos: [0.3, -0.8, 0], parent: 'rightHip' },{ name: 'rightCalf', pos: [0.3, -1.6, 0], parent: 'rightThigh' },{ name: 'rightFoot', pos: [0.3, -2.2, 0.2], parent: 'rightCalf' }];// 创建骨骼对象const boneMap = {};boneStructure.forEach(bone => {const boneObject = new THREE.Object3D();boneObject.position.set(bone.pos[0], bone.pos[1], bone.pos[2]);boneObject.name = bone.name;boneMap[bone.name] = boneObject;this.bones.push(boneObject);// 创建关节可视化const jointGeometry = new THREE.SphereGeometry(0.08, 8, 8);const jointMaterial = new THREE.MeshBasicMaterial({ color: 0x4488ff });const joint = new THREE.Mesh(jointGeometry, jointMaterial);boneObject.add(joint);this.joints.push(joint);scene.add(boneObject);});// 建立父子关系boneStructure.forEach(bone => {if (bone.parent) {const parentBone = boneMap[bone.parent];const childBone = boneMap[bone.name];parentBone.add(childBone);// 创建骨骼连接线const lineGeometry = new THREE.BufferGeometry();const lineVertices = new Float32Array([0, 0, 0,childBone.position.x - parentBone.position.x,childBone.position.y - parentBone.position.y,childBone.position.z - parentBone.position.z]);lineGeometry.setAttribute('position', new THREE.BufferAttribute(lineVertices, 3));const lineMaterial = new THREE.LineBasicMaterial({ color: 0x00ff88 });const line = new THREE.Line(lineGeometry, lineMaterial);parentBone.add(line);}});this.boneMap = boneMap;// 创建简单的蒙皮网格this.createSkinMesh();}createSkinMesh() {// 身体部位的简单网格const bodyParts = [{ name: 'torso', bone: 'chest', size: [0.8, 1.2, 0.4], offset: [0, -0.3, 0] },{ name: 'head', bone: 'head', size: [0.4, 0.4, 0.4], offset: [0, 0, 0] },{ name: 'leftArm', bone: 'leftArm', size: [0.6, 0.15, 0.15], offset: [-0.3, 0, 0] },{ name: 'rightArm', bone: 'rightArm', size: [0.6, 0.15, 0.15], offset: [0.3, 0, 0] },{ name: 'leftThigh', bone: 'leftThigh', size: [0.2, 0.7, 0.2], offset: [0, -0.35, 0] },{ name: 'rightThigh', bone: 'rightThigh', size: [0.2, 0.7, 0.2], offset: [0, -0.35, 0] },{ name: 'leftCalf', bone: 'leftCalf', size: [0.15, 0.6, 0.15], offset: [0, -0.3, 0] },{ name: 'rightCalf', bone: 'rightCalf', size: [0.15, 0.6, 0.15], offset: [0, -0.3, 0] }];bodyParts.forEach(part => {const geometry = new THREE.BoxGeometry(part.size[0], part.size[1], part.size[2]);const material = new THREE.MeshPhongMaterial({ color: 0xff4444, transparent: true, opacity: 0.7 });const mesh = new THREE.Mesh(geometry, material);mesh.position.set(part.offset[0], part.offset[1], part.offset[2]);if (this.boneMap[part.bone]) {this.boneMap[part.bone].add(mesh);this.meshes.push(mesh);}});}// 动画函数animate(action, time, weight = 1.0) {const t = time * animationSpeed;switch(action) {case 'idle':this.animateIdle(t, weight);break;case 'walk':this.animateWalk(t, weight);break;case 'run':this.animateRun(t, weight);break;case 'jump':this.animateJump(t, weight);break;}}animateIdle(time, weight) {const breathe = Math.sin(time * 2) * 0.02 * weight;const sway = Math.sin(time * 0.8) * 0.01 * weight;if (this.boneMap['chest']) {this.boneMap['chest'].rotation.z = sway;this.boneMap['chest'].position.y = 2 + breathe;}if (this.boneMap['head']) {this.boneMap['head'].rotation.x = Math.sin(time * 1.5) * 0.05 * weight;}}animateWalk(time, weight) {const walkCycle = time * 3;const armSwing = Math.sin(walkCycle) * 0.3 * weight;const legSwing = Math.sin(walkCycle) * 0.4 * weight;const bodyBob = Math.abs(Math.sin(walkCycle * 2)) * 0.05 * weight;// 手臂摆动if (this.boneMap['leftArm']) {this.boneMap['leftArm'].rotation.x = armSwing;}if (this.boneMap['rightArm']) {this.boneMap['rightArm'].rotation.x = -armSwing;}// 腿部运动if (this.boneMap['leftThigh']) {this.boneMap['leftThigh'].rotation.x = legSwing;}if (this.boneMap['rightThigh']) {this.boneMap['rightThigh'].rotation.x = -legSwing;}// 身体上下摆动if (this.boneMap['root']) {this.boneMap['root'].position.y = bodyBob;}}animateRun(time, weight) {const runCycle = time * 6;const armSwing = Math.sin(runCycle) * 0.6 * weight;const legSwing = Math.sin(runCycle) * 0.8 * weight;const bodyBob = Math.abs(Math.sin(runCycle * 2)) * 0.15 * weight;const bodyLean = Math.sin(runCycle) * 0.1 * weight;// 更大的手臂摆动if (this.boneMap['leftArm']) {this.boneMap['leftArm'].rotation.x = armSwing;}if (this.boneMap['rightArm']) {this.boneMap['rightArm'].rotation.x = -armSwing;}// 更大的腿部运动if (this.boneMap['leftThigh']) {this.boneMap['leftThigh'].rotation.x = legSwing;}if (this.boneMap['rightThigh']) {this.boneMap['rightThigh'].rotation.x = -legSwing;}// 身体前倾和上下摆动if (this.boneMap['root']) {this.boneMap['root'].position.y = bodyBob;}if (this.boneMap['chest']) {this.boneMap['chest'].rotation.x = bodyLean * 0.3;}}animateJump(time, weight) {const jumpPhase = (time % 2) / 2; // 2秒一个循环let jumpY = 0;let armPose = 0;let legBend = 0;if (jumpPhase < 0.3) { // 准备阶段const t = jumpPhase / 0.3;jumpY = -0.2 * t * weight;legBend = 0.5 * t * weight;armPose = -0.5 * t * weight;} else if (jumpPhase < 0.7) { // 跳跃阶段const t = (jumpPhase - 0.3) / 0.4;const arc = Math.sin(t * Math.PI);jumpY = (arc * 1.5 - 0.2) * weight;legBend = (0.5 - arc * 0.3) * weight;armPose = (arc * 1.2 - 0.5) * weight;} else { // 着陆阶段const t = (jumpPhase - 0.7) / 0.3;jumpY = -0.1 * (1 - t) * weight;legBend = 0.2 * (1 - t) * weight;armPose = 0.7 * (1 - t) * weight;}if (this.boneMap['root']) {this.boneMap['root'].position.y = jumpY;}if (this.boneMap['leftArm']) {this.boneMap['leftArm'].rotation.x = armPose;}if (this.boneMap['rightArm']) {this.boneMap['rightArm'].rotation.x = armPose;}if (this.boneMap['leftThigh']) {this.boneMap['leftThigh'].rotation.x = -legBend;}if (this.boneMap['rightThigh']) {this.boneMap['rightThigh'].rotation.x = -legBend;}}update(deltaTime) {this.time += deltaTime;// 更新动画权重(混合)for (let action in animations) {const anim = animations[action];if (action === currentAction) {anim.weight = Math.min(1.0, anim.weight + deltaTime * blendSpeed);} else {anim.weight = Math.max(0.0, anim.weight - deltaTime * blendSpeed);}}// 清除之前的变换this.resetBones();// 应用混合动画for (let action in animations) {const anim = animations[action];if (anim.weight > 0.001) {this.animate(action, this.time * anim.speed, anim.weight);}}}resetBones() {// 重置关键骨骼的变换const resetBones = ['root', 'chest', 'head', 'leftArm', 'rightArm', 'leftThigh', 'rightThigh'];resetBones.forEach(boneName => {if (this.boneMap[boneName]) {this.boneMap[boneName].rotation.set(0, 0, 0);if (boneName === 'root') {this.boneMap[boneName].position.set(0, 0, 0);} else if (boneName === 'chest') {this.boneMap[boneName].position.set(0, 2, 0);}}});}}// 初始化场景function init() {// 创建场景scene = new THREE.Scene();scene.background = new THREE.Color(0x222244);// 创建摄像机camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.set(5, 2, 5);camera.lookAt(0, 1, 0);// 创建渲染器renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.shadowMap.enabled = true;renderer.shadowMap.type = THREE.PCFSoftShadowMap;document.getElementById('container').appendChild(renderer.domElement);// 添加灯光const ambientLight = new THREE.AmbientLight(0x404040, 0.6);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(5, 10, 5);directionalLight.castShadow = true;directionalLight.shadow.mapSize.width = 1024;directionalLight.shadow.mapSize.height = 1024;scene.add(directionalLight);// 添加地面const groundGeometry = new THREE.PlaneGeometry(20, 20);const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x336633 });const ground = new THREE.Mesh(groundGeometry, groundMaterial);ground.rotation.x = -Math.PI / 2;ground.position.y = -2.5;ground.receiveShadow = true;scene.add(ground);// 创建角色character = new SimpleSkeleton();// 设置控件setupControls();// 开始动画循环animate();}function setupControls() {// 动作按钮const actionButtons = ['idle', 'walk', 'run', 'jump'];actionButtons.forEach(action => {const button = document.getElementById(action);button.addEventListener('click', () => {if (!isAutoMode) {setAction(action);updateActiveButton(action);}});});// 滑块控制const blendSpeedSlider = document.getElementById('blendSpeed');const blendSpeedValue = document.getElementById('blendSpeedValue');blendSpeedSlider.addEventListener('input', (e) => {blendSpeed = parseFloat(e.target.value);blendSpeedValue.textContent = blendSpeed.toFixed(1);});const animSpeedSlider = document.getElementById('animSpeed');const animSpeedValue = document.getElementById('animSpeedValue');animSpeedSlider.addEventListener('input', (e) => {animationSpeed = parseFloat(e.target.value);animSpeedValue.textContent = animationSpeed.toFixed(1);});// 自动模式const autoButton = document.getElementById('autoMode');autoButton.addEventListener('click', () => {isAutoMode = !isAutoMode;autoButton.textContent = isAutoMode ? '手动模式' : '自动模式';autoButton.classList.toggle('active');if (!isAutoMode) {updateActiveButton(currentAction);}});}function setAction(action) {currentAction = action;}function updateActiveButton(action) {document.querySelectorAll('#controls button').forEach(btn => {btn.classList.remove('active');});document.getElementById(action).classList.add('active');}let lastTime = 0;function animate() {requestAnimationFrame(animate);const currentTime = performance.now() * 0.001;const deltaTime = currentTime - lastTime;lastTime = currentTime;// 自动模式切换动作if (isAutoMode) {autoModeTimer += deltaTime;if (autoModeTimer > 3) { // 每3秒切换一次动作const actions = ['idle', 'walk', 'run', 'jump'];const currentIndex = actions.indexOf(currentAction);const nextAction = actions[(currentIndex + 1) % actions.length];setAction(nextAction);autoModeTimer = 0;}}// 更新角色动画if (character) {character.update(deltaTime);}// 摄像机环绕const time = currentTime * 0.3;camera.position.x = Math.cos(time) * 6;camera.position.z = Math.sin(time) * 6;camera.position.y = 3;camera.lookAt(0, 1, 0);renderer.render(scene, camera);}// 窗口大小调整window.addEventListener('resize', () => {const container = document.getElementById('container');const containerWidth = container.offsetWidth;const containerHeight = container.offsetHeight;camera.aspect = containerWidth / containerHeight;camera.updateProjectionMatrix();renderer.setSize(containerWidth, containerHeight);});// 启动应用init();</script><div id="monica-content-root" class="monica-widget" style="pointer-events: auto;"></div></body><div id="immersive-translate-popup" style="all: initial"></div></html>

在这里插入图片描述
需要全屏演示的同学可以通过**骨骼动画混合演示**查看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/96043.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/96043.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python + Flask模块学习 1 基础用法

目录 Flask 的主要作用 常用扩展 Flask 基本用法 1. 安装 Flask&#xff08;再安装个postman用来调试测试API哈 2. 最小化应用示例 3. 运行应用 Flask 是一个轻量级的 Python Web 框架&#xff0c;它简洁灵活&#xff0c;适合快速开发 Web 应用和 API。它被称为 "微…

python数据可视化之Matplotlib(8)-Matplotlib样式系统深度解析:从入门到企业级应用

作者&#xff1a;浪浪山齐天大圣 描述&#xff1a;深入探索Matplotlib样式系统的核心机制&#xff0c;掌握从基础样式到企业级样式管理的完整解决方案引言 在数据可视化的世界里&#xff0c;一个优秀的图表不仅要准确传达数据信息&#xff0c;更要具备专业的视觉效果。Matplotl…

3.HTTP/HTTPS:报文格式、方法、状态码、缓存、SSLTLS握手

HTTP/HTTPS&#xff1a;报文格式、方法、状态码、缓存、SSL/TLS握手 1. HTTP报文格式 1.1 HTTP请求报文(Request) GET /api/v1/users HTTP/1.1 // 请求行&#xff1a;方法、URI、协议版本 Host: api.example.com // 请求头 (Headers) User-Agent: Mozil…

【慢教程】Ollama4:ollama命令汇总

ℹ️教程说明 Ollama 是一款轻量级本地大模型部署工具&#xff0c;使用广泛&#xff0c;且容易上手&#xff0c;适合作为AI技术的入门。 &#x1f9e9;教程各部分链接&#xff1a; 第一课&#xff1a;ollama运行原理介绍及同类工具对比 ollama运行原理介绍及同类工具对比&am…

JAVA Predicate

简单来说&#xff0c;当我明确知道此次判断的逻辑时就可以直接使用if&#xff0c;但是我这次的判断逻辑可能会随着某个参数变化的时候使用Predicate比如当我想要判断某长段文字中是否包含list<String> 中的元素&#xff0c;并且包含的元素个数大于 list<String>最后…

什么是PFC控制器

一句话概括PFC控制器是一种智能芯片&#xff0c;它通过控制电路中的电流波形&#xff0c;使其与电压波形保持一致&#xff0c;从而减少电力浪费&#xff0c;提高电能的利用效率。PFC控制器IC的核心作用就是控制一颗功率MOSFET的开关&#xff0c;通过特定的电路拓扑&#xff08;…

【P03_AI大模型测试之_定制化 AI 应用程序开发】

git clone https://gitee.com/winner21/aigc-test.git 类似于joycoder的&#xff0c;可以安装在vscode上的通义灵码&#xff1a;https://lingma.aliyun.com/ 1、VSCODE上配置通义灵码 2、创建前后端文件&#xff0c;并引用AI编码代码 3、指定文件&#xff0c;利用AI进行代码优…

人工智能机器学习——决策树、异常检测、主成分分析(PCA)

一、决策树(Decision Tree) 决策树&#xff1a;一种对实例进行分类的树形结构&#xff0c;通过多层判断区分目标所属类别 本质&#xff1a;通过多层判断&#xff0c;从训练数据集中归纳出一组分类规则 优点&#xff1a; 计算量小&#xff0c;运算速度快易于理解&#xff0c;可…

服务器文件同步用哪个工具?介绍一种安全高效的文件同步方案

服务器作为企业核心数据和应用的载体&#xff0c;服务器文件同步已成为IT运维、数据备份、业务协同中不可或缺的一环。然而&#xff0c;面对多样的场景和严苛的需求&#xff0c;选择一个既高效又安全的服务器文件同步工具并非易事。本文将首先探讨服务器文件同步的常见场景、需…

LeetCode 004. 寻找两个正序数组的中位数 - 二分切分与分治详解

一、文章标题 LeetCode 004. 寻找两个正序数组的中位数 - 二分切分与分治详解 二、文章内容 1. 题目概述 题目描述&#xff1a;给定两个已按非降序排列的整数数组 nums1、nums2&#xff0c;设它们长度分别为 m 和 n&#xff0c;要求返回这两个数组合并后有序序列的中位数。…

预闪为什么可以用来防红眼?

打闪拍照红眼产生的原因 预闪可以用来防红眼&#xff0c;是基于人眼的生理特性和红眼现象的产生原理。在光线较暗时&#xff0c;人眼的瞳孔会放大。当使用闪光灯拍摄时&#xff0c;如果直接进行高强度闪光&#xff0c;由于瞳孔来不及缩小&#xff0c;闪光灯的光线会反射在眼球血…

Python程序使用了Ffmpeg,结束程序后,文件夹中仍然生成音频、视频文件

FFmpeg是一套可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec&#xff0c;为了保证高可移植性和编解码质量&#xff0…

模块与包的导入

077-模块-06-模块搜索顺序_哔哩哔哩_bilibili 080-包-01-包的概念以及建立包的方式_哔哩哔哩_bilibili 088-文件操作-01-文件操作套路以及Python中的对应函数和方法_哔哩哔哩_bilibili 注&#xff1a; 1.import math和 from math import *区别 2. 模块&#xff08;Module…

Docker Compose 多种安装方式 (Alibaba Cloud Linux 3 环境)

Docker Compose 多种安装方式&#xff0c;适用于不同场景&#xff08;如依赖系统包管理器、使用 Python 工具链、集成 Docker 插件等&#xff09;。以下是常见的方案&#xff0c;尤其针对 Alibaba Cloud Linux 3 环境适配&#xff1a; 一、二进制包安装&#xff08;推荐&#…

Dubbo3序列化安全机制导致的一次生产故障

前言 记录一次 Dubbo 线上故障排查和原因分析。 线上 Dubbo 消费者启动有错误日志如下&#xff0c;但是不影响服务启动。 java.lang.TypeNotPresentException: Type org.example.model.ThirdParam not present ... Caused by: java.lang.ClassNotFoundException: org.example.m…

centos7 docker离线安装

介绍 本文主要讲了如何在完全没网的情况下安装docker&#xff08;适合于高网络安全要求的企业&#xff09; 本文适用的centos版本&#xff1a; [root0001 temp]# cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) 采用docker in docker下载依赖 实际试验后&…

东京本社招聘 | 财务负责人 多个日本IT岗位(Java/C++/Python/AWS 等),IT营业同步招募

大家好&#xff0c;本期为大家带来我司在东京GSD本社及其他会社千叶地区的招聘岗位。 涵盖 财务负责人、Java开发工程师、数据中心维护工程师、项目经理、IT营业 等多个职位。 欢迎有志之士加入&#xff01;&#x1f539; 财务负责人&#xff08;東京本社&#xff09;工作内容日…

四数之和

目录 一&#xff1a;题目链接 二&#xff1a;题目思路 三&#xff1a;代码实现 一&#xff1a;题目链接 理解题目需要注意&#xff0c;如果两个四元组元素一一对应&#xff0c;则认为两个四元组重复&#xff0c;选择其中一个四元组即可。比如 [ 0 , 1 , 0 , 2] 和 [ 1 , …

【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架

Spring Cloud Task作为微服务架构中的轻量级任务调度框架&#xff0c;为开发人员提供了一种构建短生命周期微服务任务的便捷方式。它允许开发者快速创建、执行和管理一次性任务或短期批处理作业&#xff0c;任务执行完成后自动关闭以释放系统资源&#xff0c;避免了传统长期运行…

【1分钟速通】 HTML快速入门

HTML&#xff08;HyperText Markup Language&#xff0c;超文本标记语言&#xff09; 是构建网页的基础语言。它通过 标签&#xff08;Tag&#xff09; 来描述网页的结构和内容&#xff0c;常与 CSS&#xff08;负责样式 – <style></style>&#xff09;和 JavaScr…