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
英文地址,請注明出處:http://en.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…