mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
Merge pull request #2938 from xinnan-tech/test_live2d
update:测试页面增加切换live2d模型、拍照识物功能
This commit is contained in:
@@ -29,7 +29,15 @@ class VisionHandler(BaseHandler):
|
||||
|
||||
def _verify_auth_token(self, request) -> Tuple[bool, Optional[str]]:
|
||||
"""验证认证token"""
|
||||
# 测试模式:允许特定测试令牌或跳过验证
|
||||
auth_header = request.headers.get("Authorization", "")
|
||||
client_id = request.headers.get("Client-Id", "")
|
||||
|
||||
# 允许测试客户端跳过认证
|
||||
if client_id == "web_test_client":
|
||||
device_id = request.headers.get("Device-Id", "test_device")
|
||||
return True, device_id
|
||||
|
||||
if not auth_header.startswith("Bearer "):
|
||||
return False, None
|
||||
|
||||
|
||||
@@ -108,26 +108,68 @@ body {
|
||||
animation: shadow-float 1.26s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.loading-char-float .main-char span:nth-child(1) { animation-delay: 0s; }
|
||||
.loading-char-float .main-char span:nth-child(2) { animation-delay: 0.11s; }
|
||||
.loading-char-float .main-char span:nth-child(3) { animation-delay: 0.22s; }
|
||||
.loading-char-float .main-char span:nth-child(4) { animation-delay: 0.33s; }
|
||||
.loading-char-float .main-char span:nth-child(5) { animation-delay: 0.44s; }
|
||||
.loading-char-float .main-char span:nth-child(6) { animation-delay: 0.55s; }
|
||||
.loading-char-float .main-char span:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.loading-char-float .main-char span:nth-child(2) {
|
||||
animation-delay: 0.11s;
|
||||
}
|
||||
|
||||
.loading-char-float .main-char span:nth-child(3) {
|
||||
animation-delay: 0.22s;
|
||||
}
|
||||
|
||||
.loading-char-float .main-char span:nth-child(4) {
|
||||
animation-delay: 0.33s;
|
||||
}
|
||||
|
||||
.loading-char-float .main-char span:nth-child(5) {
|
||||
animation-delay: 0.44s;
|
||||
}
|
||||
|
||||
.loading-char-float .main-char span:nth-child(6) {
|
||||
animation-delay: 0.55s;
|
||||
}
|
||||
|
||||
@keyframes scan {
|
||||
0% { background-position: 0 0; }
|
||||
100% { background-position: 0 100px; }
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 0 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes char-float {
|
||||
0%, 100% { transform: translateY(0); opacity: 1; text-shadow: 0 0 5px #5865f2, 0 0 10px rgba(88, 101, 242, 0.8); }
|
||||
50% { transform: translateY(-5px); opacity: 0.9; text-shadow: 0 0 10px #5865f2, 0 0 20px rgba(88, 101, 242, 0.8); }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
text-shadow: 0 0 5px #5865f2, 0 0 10px rgba(88, 101, 242, 0.8);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-5px);
|
||||
opacity: 0.9;
|
||||
text-shadow: 0 0 10px #5865f2, 0 0 20px rgba(88, 101, 242, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shadow-float {
|
||||
0%, 100% { transform: translateY(3px); opacity: 0.3; }
|
||||
50% { transform: translateY(8px); opacity: 0.15; }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(3px);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(8px);
|
||||
opacity: 0.15;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== Live2D显示区域 ==================== */
|
||||
@@ -402,6 +444,15 @@ body {
|
||||
background: rgba(237, 66, 69, 1);
|
||||
}
|
||||
|
||||
/* 摄像头按钮特殊样式 */
|
||||
#cameraBtn.camera-active {
|
||||
background: rgba(237, 66, 69, 0.9);
|
||||
}
|
||||
|
||||
#cameraBtn.camera-active:hover {
|
||||
background: rgba(237, 66, 69, 1);
|
||||
}
|
||||
|
||||
/* 录音按钮脉冲动画 */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
@@ -761,6 +812,72 @@ body {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.model-select {
|
||||
width: 100%;
|
||||
padding: 10px 40px 10px 14px;
|
||||
border: 1px solid #40444b;
|
||||
border-radius: 8px;
|
||||
background: #40444b;
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 12px center;
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
.model-select:hover {
|
||||
border-color: #5865f2;
|
||||
}
|
||||
|
||||
.model-select:focus {
|
||||
outline: none;
|
||||
border-color: #5865f2;
|
||||
}
|
||||
|
||||
.model-select option {
|
||||
background: #2f3136;
|
||||
color: #ffffff;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.background-btn {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid #40444b;
|
||||
border-radius: 8px;
|
||||
background: #40444b;
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.background-btn:hover {
|
||||
background: #4f545c;
|
||||
}
|
||||
|
||||
.background-btn:active {
|
||||
background: #40444b;
|
||||
}
|
||||
|
||||
.background-btn .btn-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
/* ==================== MCP工具样式 ==================== */
|
||||
.mcp-tools-container {
|
||||
background: #2f3136;
|
||||
@@ -911,35 +1028,174 @@ body {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.property-item {
|
||||
.properties-container #addMcpPropertyBtn {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.properties-container #addMcpPropertyBtn:hover {
|
||||
background: #4752c4;
|
||||
}
|
||||
|
||||
.mcp-checkbox-label {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
color: #b9bbbe;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.property-item input {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #40444b;
|
||||
border-radius: 4px;
|
||||
background: #40444b;
|
||||
.mcp-checkbox-label input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mcp-property-name {
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.remove-property {
|
||||
background: #ed4245;
|
||||
.mcp-property-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mcp-properties-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
min-height: 60px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.input-group-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.input-group-header label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.input-group-header .properties-btn-primary {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mcp-empty-state {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.properties-btn-primary {
|
||||
background: #2196f3;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 8px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.remove-property:hover {
|
||||
background: #c03537;
|
||||
.properties-btn-primary:hover {
|
||||
background: #2196f3;
|
||||
}
|
||||
|
||||
.mcp-property-card {
|
||||
background: #36393f;
|
||||
border: 1px solid #40444b;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.mcp-property-card:hover {
|
||||
background: #40444b;
|
||||
border-color: #5865f2;
|
||||
}
|
||||
|
||||
.mcp-property-card:active {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.mcp-property-row-label {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 6px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mcp-property-label {
|
||||
color: #b9bbbe;
|
||||
font-size: 13px;
|
||||
width: 60px;
|
||||
flex-shrink: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mcp-property-value {
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.mcp-property-required-badge {
|
||||
color: #f44336;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mcp-property-row-action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 6px;
|
||||
padding-top: 6px;
|
||||
border-top: 1px solid #40444b;
|
||||
}
|
||||
|
||||
.mcp-property-delete-btn {
|
||||
background: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.mcp-property-delete-btn:hover {
|
||||
background: #d32f2f;
|
||||
}
|
||||
|
||||
.mcp-property-card-optional {
|
||||
background: #4f545c;
|
||||
color: #b9bbbe;
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.property-modal {
|
||||
max-width: 450px;
|
||||
}
|
||||
|
||||
/* ==================== 音频可视化器样式 ==================== */
|
||||
@@ -1125,4 +1381,41 @@ body {
|
||||
.modal-body::-webkit-scrollbar-thumb:hover,
|
||||
.mcp-tools-list::-webkit-scrollbar-thumb:hover {
|
||||
background: #4f545c;
|
||||
}
|
||||
|
||||
/* ==================== 摄像头显示区域样式 ==================== */
|
||||
.camera-container {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
width: 240px;
|
||||
height: 180px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.camera-container.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.camera-container.dragging {
|
||||
cursor: grabbing;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
|
||||
border-color: rgba(88, 101, 242, 0.5);
|
||||
}
|
||||
|
||||
#cameraVideo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
background: #1a1a1a;
|
||||
pointer-events: none;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
@@ -6,12 +6,25 @@ import { initMcpTools } from './core/mcp/tools.js?v=0127';
|
||||
import { uiController } from './ui/controller.js?v=0127';
|
||||
import { log } from './utils/logger.js?v=0127';
|
||||
|
||||
// 辅助函数:将Base64数据转换为Blob
|
||||
function dataURItoBlob(dataURI) {
|
||||
const byteString = atob(dataURI.split(',')[1]);
|
||||
const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
|
||||
const ab = new ArrayBuffer(byteString.length);
|
||||
const ia = new Uint8Array(ab);
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
ia[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
return new Blob([ab], { type: mimeString });
|
||||
}
|
||||
|
||||
// 应用类
|
||||
class App {
|
||||
constructor() {
|
||||
this.uiController = null;
|
||||
this.audioPlayer = null;
|
||||
this.live2dManager = null;
|
||||
this.cameraStream = null;
|
||||
}
|
||||
|
||||
// 初始化应用
|
||||
@@ -31,8 +44,12 @@ class App {
|
||||
initMcpTools();
|
||||
// 检查麦克风可用性
|
||||
await this.checkMicrophoneAvailability();
|
||||
// 检查摄像头可用性
|
||||
this.checkCameraAvailability();
|
||||
// 初始化Live2D
|
||||
await this.initLive2D();
|
||||
// 初始化摄像头
|
||||
this.initCamera();
|
||||
// 关闭加载loading
|
||||
this.setModelLoadingStatus(false);
|
||||
log('应用初始化完成', 'success');
|
||||
@@ -99,6 +116,204 @@ class App {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查摄像头可用性
|
||||
checkCameraAvailability() {
|
||||
window.cameraAvailable = true;
|
||||
log('摄像头可用性检查完成: 默认已绑定验证码', 'success');
|
||||
}
|
||||
|
||||
// 初始化摄像头
|
||||
async initCamera() {
|
||||
const cameraContainer = document.getElementById('cameraContainer');
|
||||
const cameraVideo = document.getElementById('cameraVideo');
|
||||
|
||||
if (!cameraContainer || !cameraVideo) {
|
||||
log('摄像头元素未找到,跳过初始化', 'warning');
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
let isDragging = false;
|
||||
let currentX, currentY, initialX, initialY;
|
||||
let xOffset = 0, yOffset = 0;
|
||||
|
||||
cameraContainer.addEventListener('mousedown', dragStart);
|
||||
document.addEventListener('mousemove', drag);
|
||||
document.addEventListener('mouseup', dragEnd);
|
||||
cameraContainer.addEventListener('touchstart', dragStart, { passive: false });
|
||||
document.addEventListener('touchmove', drag, { passive: false });
|
||||
document.addEventListener('touchend', dragEnd);
|
||||
|
||||
function dragStart(e) {
|
||||
if (e.type === 'touchstart') {
|
||||
initialX = e.touches[0].clientX - xOffset;
|
||||
initialY = e.touches[0].clientY - yOffset;
|
||||
} else {
|
||||
initialX = e.clientX - xOffset;
|
||||
initialY = e.clientY - yOffset;
|
||||
}
|
||||
isDragging = true;
|
||||
cameraContainer.classList.add('dragging');
|
||||
}
|
||||
|
||||
function drag(e) {
|
||||
if (isDragging) {
|
||||
e.preventDefault();
|
||||
if (e.type === 'touchmove') {
|
||||
currentX = e.touches[0].clientX - initialX;
|
||||
currentY = e.touches[0].clientY - initialY;
|
||||
} else {
|
||||
currentX = e.clientX - initialX;
|
||||
currentY = e.clientY - initialY;
|
||||
}
|
||||
xOffset = currentX;
|
||||
yOffset = currentY;
|
||||
cameraContainer.style.transform = `translate3d(${currentX}px, ${currentY}px, 0)`;
|
||||
}
|
||||
}
|
||||
|
||||
function dragEnd() {
|
||||
initialX = currentX;
|
||||
initialY = currentY;
|
||||
isDragging = false;
|
||||
cameraContainer.classList.remove('dragging');
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
window.startCamera = async () => {
|
||||
try {
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||
log('浏览器不支持摄像头API', 'warning');
|
||||
return false;
|
||||
}
|
||||
log('正在请求摄像头权限...', 'info');
|
||||
this.cameraStream = await navigator.mediaDevices.getUserMedia({
|
||||
video: { width: 320, height: 240, facingMode: 'user' },
|
||||
audio: false
|
||||
});
|
||||
cameraVideo.srcObject = this.cameraStream;
|
||||
cameraContainer.classList.add('active');
|
||||
log('摄像头已启动', 'success');
|
||||
return true;
|
||||
} catch (error) {
|
||||
log(`启动摄像头失败: ${error.name} - ${error.message}`, 'error');
|
||||
if (error.name === 'NotAllowedError') {
|
||||
log('摄像头权限被拒绝,请检查浏览器设置', 'warning');
|
||||
} else if (error.name === 'NotFoundError') {
|
||||
log('未找到摄像头设备', 'warning');
|
||||
} else if (error.name === 'NotReadableError') {
|
||||
log('摄像头已被其他程序占用', 'warning');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
window.stopCamera = () => {
|
||||
if (this.cameraStream) {
|
||||
this.cameraStream.getTracks().forEach(track => track.stop());
|
||||
this.cameraStream = null;
|
||||
cameraVideo.srcObject = null;
|
||||
log('摄像头已关闭', 'info');
|
||||
}
|
||||
};
|
||||
|
||||
window.takePhoto = (question = '描述一下看到的物品') => {
|
||||
return new Promise(async (resolve) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const video = cameraVideo;
|
||||
|
||||
if (!video || video.readyState !== video.HAVE_ENOUGH_DATA) {
|
||||
log('无法拍照:摄像头未就绪', 'warning');
|
||||
resolve({
|
||||
success: false,
|
||||
error: '摄像头未就绪,请确保已连接且摄像头已启动'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.width = video.videoWidth || 320;
|
||||
canvas.height = video.videoHeight || 240;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
const photoData = canvas.toDataURL('image/jpeg', 0.8);
|
||||
log(`拍照成功,图像数据长度: ${photoData.length}`, 'success');
|
||||
|
||||
try {
|
||||
const xz_tester_vision = localStorage.getItem('xz_tester_vision');
|
||||
if (xz_tester_vision) {
|
||||
let visionInfo = null;
|
||||
|
||||
try {
|
||||
visionInfo = JSON.parse(xz_tester_vision);
|
||||
} catch (err) {
|
||||
throw new Error(`视觉配置解析失败`);
|
||||
}
|
||||
|
||||
const { url, token } = visionInfo || {};
|
||||
if (!url || !token) {
|
||||
throw new Error('视觉分析失败:配置缺少接口地址(url)或令牌(token)');
|
||||
}
|
||||
|
||||
log(`正在发送图片到视觉分析接口: ${url}`, 'info');
|
||||
|
||||
const deviceId = document.getElementById('deviceMac')?.value || '';
|
||||
const clientId = document.getElementById('clientId')?.value || 'web_test_client';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('question', question);
|
||||
formData.append('image', dataURItoBlob(photoData), 'photo.jpg');
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'Device-Id': deviceId,
|
||||
'Client-Id': clientId,
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const analysisResult = await response.json();
|
||||
log(`视觉分析完成: ${JSON.stringify(analysisResult).substring(0, 200)}...`, 'success');
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
message: question,
|
||||
photo_data: photoData,
|
||||
photo_width: canvas.width,
|
||||
photo_height: canvas.height,
|
||||
vision_analysis: analysisResult
|
||||
});
|
||||
} else {
|
||||
log('未配置视觉分析服务', 'warning');
|
||||
}
|
||||
} catch (error) {
|
||||
log(`视觉分析失败: ${error.message}`, 'error');
|
||||
resolve({
|
||||
success: true,
|
||||
message: question,
|
||||
photo_data: photoData,
|
||||
photo_width: canvas.width,
|
||||
photo_height: canvas.height,
|
||||
vision_analysis: {
|
||||
success: false,
|
||||
error: error.message,
|
||||
fallback: '无法连接到视觉分析服务'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
log('摄像头初始化完成', 'success');
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 创建并启动应用
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
[
|
||||
{
|
||||
"name": "self_camera_take_photo",
|
||||
"description": "Take a photo using the device's camera. This tool captures the current camera frame and returns the image data. You MUST call this tool when user asks to 'take a photo', 'what do you see', 'describe what you see', or similar requests. After getting the photo data, describe what you see in the image. Parameter 'question' is optional, defaults to 'describe objects seen'.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"question": {
|
||||
"type": "string",
|
||||
"description": "Question to guide the photo analysis, e.g., 'describe the objects seen'. Can be empty."
|
||||
}
|
||||
}
|
||||
},
|
||||
"mockResponse": {
|
||||
"success": true,
|
||||
"photo_data": "base64_image_data",
|
||||
"response": "Please describe what you see based on the returned photo_data"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "self.get_device_status",
|
||||
"description": "Provides the real-time information of the device, including the current status of the audio speaker, screen, battery, network, etc.\nUse this tool for: \n1. Answering questions about current condition (e.g. what is the current volume of the audio speaker?)\n2. As the first step to control the device (e.g. turn up / down the volume of the audio speaker, etc.)",
|
||||
|
||||
@@ -27,7 +27,16 @@ export async function initMcpTools() {
|
||||
const savedTools = localStorage.getItem('mcpTools');
|
||||
if (savedTools) {
|
||||
try {
|
||||
mcpTools = JSON.parse(savedTools);
|
||||
const parsedTools = JSON.parse(savedTools);
|
||||
// 合并默认工具和用户保存的工具,保留用户自定义的工具
|
||||
const defaultToolNames = new Set(defaultMcpTools.map(t => t.name));
|
||||
// 添加默认工具中不存在的新工具
|
||||
parsedTools.forEach(tool => {
|
||||
if (!defaultToolNames.has(tool.name)) {
|
||||
defaultMcpTools.push(tool);
|
||||
}
|
||||
});
|
||||
mcpTools = defaultMcpTools;
|
||||
} catch (e) {
|
||||
log('加载MCP工具失败,使用默认工具', 'warning');
|
||||
mcpTools = [...defaultMcpTools];
|
||||
@@ -96,94 +105,167 @@ function renderMcpTools() {
|
||||
*/
|
||||
function renderMcpProperties() {
|
||||
const container = document.getElementById('mcpPropertiesContainer');
|
||||
const emptyState = document.getElementById('mcpEmptyState');
|
||||
if (!container) {
|
||||
return; // Container not found, skip rendering
|
||||
}
|
||||
if (mcpProperties.length === 0) {
|
||||
container.innerHTML = '<div style="text-align: center; padding: 20px; color: #999; font-size: 14px;">暂无参数,点击下方按钮添加参数</div>';
|
||||
if (emptyState) {
|
||||
emptyState.style.display = 'block';
|
||||
}
|
||||
container.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
if (emptyState) {
|
||||
emptyState.style.display = 'none';
|
||||
}
|
||||
container.innerHTML = mcpProperties.map((prop, index) => `
|
||||
<div class="mcp-property-item">
|
||||
<div class="mcp-property-header">
|
||||
<span class="mcp-property-name">${prop.name}</span>
|
||||
<button type="button" onclick="window.mcpModule.deleteMcpProperty(${index})"
|
||||
style="padding: 3px 8px; border: none; border-radius: 3px; background-color: #f44336; color: white; cursor: pointer; font-size: 11px;">
|
||||
删除
|
||||
</button>
|
||||
<div class="mcp-property-card" onclick="window.mcpModule.editMcpProperty(${index})">
|
||||
<div class="mcp-property-row-label">
|
||||
<span class="mcp-property-label">参数名称</span>
|
||||
<span class="mcp-property-value">${prop.name}${prop.required ? ' <span class="mcp-property-required-badge">[必填]</span>' : ''}</span>
|
||||
</div>
|
||||
<div class="mcp-property-row">
|
||||
<div>
|
||||
<label class="mcp-small-label">参数名称 *</label>
|
||||
<input type="text" class="mcp-small-input" value="${prop.name}"
|
||||
onchange="window.mcpModule.updateMcpProperty(${index}, 'name', this.value)" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mcp-small-label">数据类型 *</label>
|
||||
<select class="mcp-small-input" onchange="window.mcpModule.updateMcpProperty(${index}, 'type', this.value)">
|
||||
<option value="string" ${prop.type === 'string' ? 'selected' : ''}>字符串</option>
|
||||
<option value="integer" ${prop.type === 'integer' ? 'selected' : ''}>整数</option>
|
||||
<option value="number" ${prop.type === 'number' ? 'selected' : ''}>数字</option>
|
||||
<option value="boolean" ${prop.type === 'boolean' ? 'selected' : ''}>布尔值</option>
|
||||
<option value="array" ${prop.type === 'array' ? 'selected' : ''}>数组</option>
|
||||
<option value="object" ${prop.type === 'object' ? 'selected' : ''}>对象</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mcp-property-row-label">
|
||||
<span class="mcp-property-label">数据类型</span>
|
||||
<span class="mcp-property-value">${getTypeLabel(prop.type)}</span>
|
||||
</div>
|
||||
${(prop.type === 'integer' || prop.type === 'number') ? `
|
||||
<div class="mcp-property-row">
|
||||
<div>
|
||||
<label class="mcp-small-label">最小值</label>
|
||||
<input type="number" class="mcp-small-input" value="${prop.minimum !== undefined ? prop.minimum : ''}"
|
||||
placeholder="可选" onchange="window.mcpModule.updateMcpProperty(${index}, 'minimum', this.value ? parseFloat(this.value) : undefined)">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mcp-small-label">最大值</label>
|
||||
<input type="number" class="mcp-small-input" value="${prop.maximum !== undefined ? prop.maximum : ''}"
|
||||
placeholder="可选" onchange="window.mcpModule.updateMcpProperty(${index}, 'maximum', this.value ? parseFloat(this.value) : undefined)">
|
||||
</div>
|
||||
<div class="mcp-property-row-label">
|
||||
<span class="mcp-property-label">描述</span>
|
||||
<span class="mcp-property-value">${prop.description || '-'}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="mcp-property-row-full">
|
||||
<label class="mcp-small-label">参数描述</label>
|
||||
<input type="text" class="mcp-small-input" value="${prop.description || ''}"
|
||||
placeholder="可选" onchange="window.mcpModule.updateMcpProperty(${index}, 'description', this.value)">
|
||||
<div class="mcp-property-row-action">
|
||||
<button class="mcp-property-delete-btn" onclick="event.stopPropagation(); window.mcpModule.deleteMcpProperty(${index})">删除</button>
|
||||
</div>
|
||||
<label class="mcp-checkbox-label">
|
||||
<input type="checkbox" ${prop.required ? 'checked' : ''}
|
||||
onchange="window.mcpModule.updateMcpProperty(${index}, 'required', this.checked)">
|
||||
必填参数
|
||||
</label>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加参数
|
||||
* 获取数据类型标签
|
||||
*/
|
||||
function addMcpProperty() {
|
||||
mcpProperties.push({ name: `param_${mcpProperties.length + 1}`, type: 'string', required: false, description: '' });
|
||||
renderMcpProperties();
|
||||
function getTypeLabel(type) {
|
||||
const typeMap = {
|
||||
'string': '字符串',
|
||||
'integer': '整数',
|
||||
'number': '数字',
|
||||
'boolean': '布尔值',
|
||||
'array': '数组',
|
||||
'object': '对象'
|
||||
};
|
||||
return typeMap[type] || type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新参数
|
||||
* 添加参数 - 打开参数编辑模态框
|
||||
*/
|
||||
function updateMcpProperty(index, field, value) {
|
||||
if (field === 'name') {
|
||||
const isDuplicate = mcpProperties.some((p, i) => i !== index && p.name === value);
|
||||
if (isDuplicate) {
|
||||
alert('参数名称已存在,请使用不同的名称');
|
||||
renderMcpProperties();
|
||||
return;
|
||||
function addMcpProperty() {
|
||||
openPropertyModal();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑参数 - 打开参数编辑模态框
|
||||
*/
|
||||
function editMcpProperty(index) {
|
||||
openPropertyModal(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开参数编辑模态框
|
||||
*/
|
||||
function openPropertyModal(index = null) {
|
||||
const form = document.getElementById('mcpPropertyForm');
|
||||
const title = document.getElementById('mcpPropertyModalTitle');
|
||||
document.getElementById('mcpPropertyIndex').value = index !== null ? index : -1;
|
||||
|
||||
if (index !== null) {
|
||||
const prop = mcpProperties[index];
|
||||
title.textContent = '编辑参数';
|
||||
document.getElementById('mcpPropertyName').value = prop.name;
|
||||
document.getElementById('mcpPropertyType').value = prop.type || 'string';
|
||||
document.getElementById('mcpPropertyMinimum').value = prop.minimum !== undefined ? prop.minimum : '';
|
||||
document.getElementById('mcpPropertyMaximum').value = prop.maximum !== undefined ? prop.maximum : '';
|
||||
document.getElementById('mcpPropertyDescription').value = prop.description || '';
|
||||
document.getElementById('mcpPropertyRequired').checked = prop.required || false;
|
||||
} else {
|
||||
title.textContent = '添加参数';
|
||||
form.reset();
|
||||
document.getElementById('mcpPropertyName').value = `param_${mcpProperties.length + 1}`;
|
||||
document.getElementById('mcpPropertyType').value = 'string';
|
||||
document.getElementById('mcpPropertyMinimum').value = '';
|
||||
document.getElementById('mcpPropertyMaximum').value = '';
|
||||
document.getElementById('mcpPropertyDescription').value = '';
|
||||
document.getElementById('mcpPropertyRequired').checked = false;
|
||||
}
|
||||
|
||||
updatePropertyRangeVisibility();
|
||||
document.getElementById('mcpPropertyModal').style.display = 'flex';
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭参数编辑模态框
|
||||
*/
|
||||
function closePropertyModal() {
|
||||
document.getElementById('mcpPropertyModal').style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数值范围输入框的可见性
|
||||
*/
|
||||
function updatePropertyRangeVisibility() {
|
||||
const type = document.getElementById('mcpPropertyType').value;
|
||||
const rangeGroup = document.getElementById('mcpPropertyRangeGroup');
|
||||
if (type === 'integer' || type === 'number') {
|
||||
rangeGroup.style.display = 'block';
|
||||
} else {
|
||||
rangeGroup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数表单提交
|
||||
*/
|
||||
function handlePropertySubmit(e) {
|
||||
e.preventDefault();
|
||||
const index = parseInt(document.getElementById('mcpPropertyIndex').value);
|
||||
const name = document.getElementById('mcpPropertyName').value.trim();
|
||||
const type = document.getElementById('mcpPropertyType').value;
|
||||
const minimum = document.getElementById('mcpPropertyMinimum').value;
|
||||
const maximum = document.getElementById('mcpPropertyMaximum').value;
|
||||
const description = document.getElementById('mcpPropertyDescription').value.trim();
|
||||
const required = document.getElementById('mcpPropertyRequired').checked;
|
||||
|
||||
// 检查名称重复
|
||||
const isDuplicate = mcpProperties.some((p, i) => i !== index && p.name === name);
|
||||
if (isDuplicate) {
|
||||
alert('参数名称已存在,请使用不同的名称');
|
||||
return;
|
||||
}
|
||||
|
||||
const propData = {
|
||||
name,
|
||||
type,
|
||||
description,
|
||||
required
|
||||
};
|
||||
|
||||
// 数值类型添加范围限制
|
||||
if (type === 'integer' || type === 'number') {
|
||||
if (minimum !== '') {
|
||||
propData.minimum = parseFloat(minimum);
|
||||
}
|
||||
if (maximum !== '') {
|
||||
propData.maximum = parseFloat(maximum);
|
||||
}
|
||||
}
|
||||
mcpProperties[index][field] = value;
|
||||
if (field === 'type' && value !== 'integer' && value !== 'number') {
|
||||
delete mcpProperties[index].minimum;
|
||||
delete mcpProperties[index].maximum;
|
||||
renderMcpProperties();
|
||||
|
||||
if (index >= 0) {
|
||||
mcpProperties[index] = propData;
|
||||
} else {
|
||||
mcpProperties.push(propData);
|
||||
}
|
||||
|
||||
renderMcpProperties();
|
||||
closePropertyModal();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,6 +288,14 @@ function setupMcpEventListeners() {
|
||||
const cancelBtn = document.getElementById('cancelMcpBtn');
|
||||
const form = document.getElementById('mcpToolForm');
|
||||
const addPropertyBtn = document.getElementById('addMcpPropertyBtn');
|
||||
|
||||
// 参数编辑模态框相关元素
|
||||
const propertyModal = document.getElementById('mcpPropertyModal');
|
||||
const closePropertyBtn = document.getElementById('closeMcpPropertyModalBtn');
|
||||
const cancelPropertyBtn = document.getElementById('cancelMcpPropertyBtn');
|
||||
const propertyForm = document.getElementById('mcpPropertyForm');
|
||||
const propertyTypeSelect = document.getElementById('mcpPropertyType');
|
||||
|
||||
// Return early if required elements don't exist (e.g., in test environment)
|
||||
if (!toggleBtn || !panel || !addBtn || !modal || !closeBtn || !cancelBtn || !form || !addPropertyBtn) {
|
||||
return;
|
||||
@@ -225,6 +315,17 @@ function setupMcpEventListeners() {
|
||||
if (e.target === modal) closeMcpModal();
|
||||
});
|
||||
form.addEventListener('submit', handleMcpSubmit);
|
||||
|
||||
// 参数编辑模态框事件
|
||||
if (propertyModal && closePropertyBtn && cancelPropertyBtn && propertyForm && propertyTypeSelect) {
|
||||
closePropertyBtn.addEventListener('click', closePropertyModal);
|
||||
cancelPropertyBtn.addEventListener('click', closePropertyModal);
|
||||
propertyModal.addEventListener('click', (e) => {
|
||||
if (e.target === propertyModal) closePropertyModal();
|
||||
});
|
||||
propertyForm.addEventListener('submit', handlePropertySubmit);
|
||||
propertyTypeSelect.addEventListener('change', updatePropertyRangeVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,7 +367,7 @@ function openMcpModal(index = null) {
|
||||
mcpProperties = [];
|
||||
}
|
||||
renderMcpProperties();
|
||||
document.getElementById('mcpToolModal').style.display = 'block';
|
||||
document.getElementById('mcpToolModal').style.display = 'flex';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -392,12 +493,26 @@ export function getMcpTools() {
|
||||
/**
|
||||
* 执行工具调用
|
||||
*/
|
||||
export function executeMcpTool(toolName, toolArgs) {
|
||||
export async function executeMcpTool(toolName, toolArgs) {
|
||||
const tool = mcpTools.find(t => t.name === toolName);
|
||||
if (!tool) {
|
||||
log(`未找到工具: ${toolName}`, 'error');
|
||||
return { success: false, error: `未知工具: ${toolName}` };
|
||||
}
|
||||
|
||||
// 处理拍照工具
|
||||
if (toolName === 'self_camera_take_photo') {
|
||||
if (typeof window.takePhoto === 'function') {
|
||||
const question = toolArgs && toolArgs.question ? toolArgs.question : '描述一下看到的物品';
|
||||
log(`正在执行拍照: ${question}`, 'info');
|
||||
const result = await window.takePhoto(question);
|
||||
return result;
|
||||
} else {
|
||||
log('拍照功能不可用', 'warning');
|
||||
return { success: false, error: '摄像头未启动或不支持拍照功能' };
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有模拟返回结果,使用它
|
||||
if (tool.mockResponse) {
|
||||
// 替换模板变量
|
||||
@@ -424,4 +539,4 @@ export function executeMcpTool(toolName, toolArgs) {
|
||||
}
|
||||
|
||||
// 暴露全局方法供 HTML 内联事件调用
|
||||
window.mcpModule = { updateMcpProperty, deleteMcpProperty, editMcpTool, deleteMcpTool };
|
||||
window.mcpModule = { addMcpProperty, editMcpProperty, deleteMcpProperty, editMcpTool, deleteMcpTool };
|
||||
|
||||
@@ -74,6 +74,9 @@ export class WebSocketHandler {
|
||||
handleTextMessage(message) {
|
||||
if (message.type === 'hello') {
|
||||
log(`服务器回应:${JSON.stringify(message, null, 2)}`, 'success');
|
||||
window.cameraAvailable = true;
|
||||
log('连接成功,摄像头已可用', 'success');
|
||||
uiController.updateDialButton(true);
|
||||
uiController.startAIChatSession();
|
||||
} else if (message.type === 'tts') {
|
||||
this.handleTTSMessage(message);
|
||||
@@ -81,6 +84,23 @@ export class WebSocketHandler {
|
||||
log(`收到音频控制消息: ${JSON.stringify(message)}`, 'info');
|
||||
} else if (message.type === 'stt') {
|
||||
log(`识别结果: ${message.text}`, 'info');
|
||||
// 检查是否需要绑定设备
|
||||
if (message.text && (message.text.includes('绑定') || message.text.includes('bind'))) {
|
||||
log('收到设备绑定提示,更新摄像头状态', 'warning');
|
||||
window.cameraAvailable = false;
|
||||
// 关闭摄像头
|
||||
if (typeof window.stopCamera === 'function') {
|
||||
window.stopCamera();
|
||||
}
|
||||
// 更新摄像头按钮状态
|
||||
const cameraBtn = document.getElementById('cameraBtn');
|
||||
if (cameraBtn) {
|
||||
cameraBtn.classList.remove('camera-active');
|
||||
cameraBtn.querySelector('.btn-text').textContent = '摄像头';
|
||||
cameraBtn.disabled = true;
|
||||
cameraBtn.title = '请先绑定验证码';
|
||||
}
|
||||
}
|
||||
// 使用新的聊天消息回调显示STT消息
|
||||
if (this.onChatMessage && message.text) {
|
||||
this.onChatMessage(message.text, true);
|
||||
@@ -249,30 +269,57 @@ export class WebSocketHandler {
|
||||
|
||||
log(`调用工具: ${toolName} 参数: ${JSON.stringify(toolArgs)}`, 'info');
|
||||
|
||||
const result = executeMcpTool(toolName, toolArgs);
|
||||
|
||||
const replyMessage = JSON.stringify({
|
||||
"session_id": message.session_id || "",
|
||||
"type": "mcp",
|
||||
"payload": {
|
||||
"jsonrpc": "2.0",
|
||||
"id": payload.id,
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": JSON.stringify(result)
|
||||
}
|
||||
],
|
||||
"isError": false
|
||||
executeMcpTool(toolName, toolArgs).then(result => {
|
||||
const replyMessage = JSON.stringify({
|
||||
"session_id": message.session_id || "",
|
||||
"type": "mcp",
|
||||
"payload": {
|
||||
"jsonrpc": "2.0",
|
||||
"id": payload.id,
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": JSON.stringify(result)
|
||||
}
|
||||
],
|
||||
"isError": false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
log(`客户端上报: ${replyMessage}`, 'info');
|
||||
this.websocket.send(replyMessage);
|
||||
log(`客户端上报: ${replyMessage}`, 'info');
|
||||
this.websocket.send(replyMessage);
|
||||
}).catch(error => {
|
||||
log(`工具执行失败: ${error.message}`, 'error');
|
||||
const errorReply = JSON.stringify({
|
||||
"session_id": message.session_id || "",
|
||||
"type": "mcp",
|
||||
"payload": {
|
||||
"jsonrpc": "2.0",
|
||||
"id": payload.id,
|
||||
"error": {
|
||||
"code": -32603,
|
||||
"message": error.message
|
||||
}
|
||||
}
|
||||
});
|
||||
this.websocket.send(errorReply);
|
||||
});
|
||||
} else if (payload.method === 'initialize') {
|
||||
log(`收到工具初始化请求: ${JSON.stringify(payload.params)}`, 'info');
|
||||
// 保存视觉分析接口地址
|
||||
const visionUrl = document.getElementById('visionUrl');
|
||||
const visionConfig = payload?.params?.capabilities?.vision;
|
||||
if (visionConfig && typeof visionConfig === 'object' && visionConfig.url && visionConfig.token) {
|
||||
const visionConfigStr = JSON.stringify(visionConfig);
|
||||
localStorage.setItem('xz_tester_vision', visionConfigStr);
|
||||
if (visionUrl) visionUrl.value = visionConfig.url;
|
||||
} else {
|
||||
localStorage.removeItem('xz_tester_vision');
|
||||
if (visionUrl) visionUrl.value = '';
|
||||
}
|
||||
|
||||
const replyMessage = JSON.stringify({
|
||||
"session_id": message.session_id || "",
|
||||
"type": "mcp",
|
||||
@@ -387,6 +434,17 @@ export class WebSocketHandler {
|
||||
|
||||
const audioRecorder = getAudioRecorder();
|
||||
audioRecorder.stop();
|
||||
|
||||
// 关闭摄像头
|
||||
if (typeof window.stopCamera === 'function') {
|
||||
window.stopCamera();
|
||||
}
|
||||
|
||||
// 隐藏摄像头显示区域
|
||||
const cameraContainer = document.getElementById('cameraContainer');
|
||||
if (cameraContainer) {
|
||||
cameraContainer.classList.remove('active');
|
||||
}
|
||||
};
|
||||
|
||||
this.websocket.onerror = (error) => {
|
||||
@@ -420,6 +478,17 @@ export class WebSocketHandler {
|
||||
this.websocket.close();
|
||||
const audioRecorder = getAudioRecorder();
|
||||
audioRecorder.stop();
|
||||
|
||||
// 关闭摄像头
|
||||
if (typeof window.stopCamera === 'function') {
|
||||
window.stopCamera();
|
||||
}
|
||||
|
||||
// 隐藏摄像头显示区域
|
||||
const cameraContainer = document.getElementById('cameraContainer');
|
||||
if (cameraContainer) {
|
||||
cameraContainer.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
// 发送文本消息
|
||||
|
||||
@@ -12,7 +12,42 @@ class Live2DManager {
|
||||
this.audioContext = null;
|
||||
this.analyser = null;
|
||||
this.dataArray = null;
|
||||
this.lastEmotionActionTime = null; // 上次情绪触发动作的时间
|
||||
this.lastEmotionActionTime = null;
|
||||
this.currentModelName = null;
|
||||
|
||||
// 模型特定配置
|
||||
this.modelConfig = {
|
||||
'hiyori_pro_zh': {
|
||||
mouthParam: 'ParamMouthOpenY',
|
||||
mouthAmplitude: 1.0,
|
||||
mouthThresholds: { low: 0.3, high: 0.7 },
|
||||
motionMap: {
|
||||
'FlickUp': 'FlickUp',
|
||||
'FlickDown': 'FlickDown',
|
||||
'Tap': 'Tap',
|
||||
'Tap@Body': 'Tap@Body',
|
||||
'Flick': 'Flick',
|
||||
'Flick@Body': 'Flick@Body'
|
||||
}
|
||||
},
|
||||
'natori_pro_zh': {
|
||||
mouthParam: 'ParamMouthOpenY',
|
||||
mouthAmplitude: 1.0,
|
||||
mouthThresholds: { low: 0.1, high: 0.4 },
|
||||
mouthFormParam: 'ParamMouthForm',
|
||||
mouthFormAmplitude: 1.0,
|
||||
mouthForm2Param: 'ParamMouthForm2',
|
||||
mouthForm2Amplitude: 0.8,
|
||||
motionMap: {
|
||||
'FlickUp': 'FlickUp',
|
||||
'FlickDown': 'Flick@Body',
|
||||
'Tap': 'Tap',
|
||||
'Tap@Body': 'Tap@Head',
|
||||
'Flick': 'Tap',
|
||||
'Flick@Body': 'Flick@Body'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 情绪到动作的映射
|
||||
this.emotionToActionMap = {
|
||||
@@ -67,10 +102,33 @@ class Live2DManager {
|
||||
const currentPath = window.location.pathname;
|
||||
const lastSlashIndex = currentPath.lastIndexOf('/');
|
||||
const basePath = currentPath.substring(0, lastSlashIndex + 1);
|
||||
const modelPath = basePath + 'hiyori_pro_zh/runtime/hiyori_pro_t11.model3.json';
|
||||
|
||||
// 从 localStorage 读取上次选择的模型,如果没有则使用默认
|
||||
const savedModelName = localStorage.getItem('live2dModel') || 'hiyori_pro_zh';
|
||||
const modelFileMap = {
|
||||
'hiyori_pro_zh': 'hiyori_pro_t11.model3.json',
|
||||
'natori_pro_zh': 'natori_pro_t06.model3.json'
|
||||
};
|
||||
const modelFileName = modelFileMap[savedModelName] || 'hiyori_pro_t11.model3.json';
|
||||
const modelPath = basePath + 'resources/' + savedModelName + '/runtime/' + modelFileName;
|
||||
|
||||
this.live2dModel = await PIXI.live2d.Live2DModel.from(modelPath);
|
||||
this.live2dApp.stage.addChild(this.live2dModel);
|
||||
|
||||
// 保存当前模型名称
|
||||
this.currentModelName = savedModelName;
|
||||
|
||||
// 更新下拉框显示
|
||||
const modelSelect = document.getElementById('live2dModelSelect');
|
||||
if (modelSelect) {
|
||||
modelSelect.value = savedModelName;
|
||||
}
|
||||
|
||||
// 设置模型特定的嘴部参数名
|
||||
if (this.modelConfig[savedModelName]) {
|
||||
this.mouthParam = this.modelConfig[savedModelName].mouthParam || 'ParamMouthOpenY';
|
||||
}
|
||||
|
||||
// 设置模型属性
|
||||
this.live2dModel.scale.set(0.33);
|
||||
this.live2dModel.x = (window.innerWidth - this.live2dModel.width) * 0.5;
|
||||
@@ -364,33 +422,88 @@ class Live2DManager {
|
||||
if (internal && internal.coreModel) {
|
||||
const coreModel = internal.coreModel;
|
||||
|
||||
// 获取音频分贝值
|
||||
let mouthValue = 0;
|
||||
let mouthOpenY = 0;
|
||||
let mouthForm = 0;
|
||||
let mouthForm2 = 0;
|
||||
let average = 0;
|
||||
|
||||
if (this.analyser && this.dataArray) {
|
||||
this.analyser.getByteFrequencyData(this.dataArray);
|
||||
average = this.dataArray.reduce((a, b) => a + b) / this.dataArray.length;
|
||||
|
||||
// 优化音量映射函数,使中等音量范围变化更明显
|
||||
// 使用S形曲线函数,在中等音量范围有更好的响应
|
||||
const normalizedVolume = average / 255;
|
||||
|
||||
// S形曲线:在0.3-0.7范围内有最大的斜率(变化最明显)
|
||||
if (normalizedVolume < 0.3) {
|
||||
// 低音量:缓慢增长
|
||||
mouthValue = Math.pow(normalizedVolume / 0.3, 1.5) * 0.3;
|
||||
} else if (normalizedVolume < 0.7) {
|
||||
// 中等音量:线性增长,变化最明显
|
||||
mouthValue = 0.3 + (normalizedVolume - 0.3) / 0.4 * 0.5;
|
||||
} else {
|
||||
// 高音量:缓慢接近最大值
|
||||
mouthValue = 0.8 + Math.pow((normalizedVolume - 0.7) / 0.3, 1.2) * 0.2;
|
||||
// 获取模型特定的阈值
|
||||
let lowThreshold = 0.3;
|
||||
let highThreshold = 0.7;
|
||||
if (this.currentModelName && this.modelConfig[this.currentModelName]) {
|
||||
lowThreshold = this.modelConfig[this.currentModelName].mouthThresholds?.low || 0.3;
|
||||
highThreshold = this.modelConfig[this.currentModelName].mouthThresholds?.high || 0.7;
|
||||
}
|
||||
|
||||
// 确保嘴部参数在0-1范围内
|
||||
mouthValue = Math.min(Math.max(mouthValue, 0), 1);
|
||||
// 使用模型特定的阈值进行映射
|
||||
let minOpenY = 0.1;
|
||||
if (this.currentModelName && this.modelConfig[this.currentModelName]) {
|
||||
minOpenY = this.modelConfig[this.currentModelName].mouthMinOpenY || 0.1;
|
||||
}
|
||||
|
||||
if (normalizedVolume < lowThreshold) {
|
||||
mouthOpenY = minOpenY + Math.pow(normalizedVolume / lowThreshold, 1.5) * (0.4 - minOpenY);
|
||||
} else if (normalizedVolume < highThreshold) {
|
||||
mouthOpenY = 0.4 + (normalizedVolume - lowThreshold) / (highThreshold - lowThreshold) * 0.4;
|
||||
} else {
|
||||
mouthOpenY = 0.8 + Math.pow((normalizedVolume - highThreshold) / (1 - highThreshold), 1.2) * 0.2;
|
||||
}
|
||||
|
||||
// 应用模型特定的嘴部开合幅度
|
||||
let amplitudeMultiplier = 1.0;
|
||||
let maxOpenY = 2.5;
|
||||
if (this.currentModelName && this.modelConfig[this.currentModelName]) {
|
||||
amplitudeMultiplier = this.modelConfig[this.currentModelName].mouthAmplitude;
|
||||
maxOpenY = this.modelConfig[this.currentModelName].maxOpenY || 2.5;
|
||||
}
|
||||
mouthOpenY = mouthOpenY * amplitudeMultiplier;
|
||||
mouthOpenY = Math.min(Math.max(mouthOpenY, 0), maxOpenY);
|
||||
|
||||
// 计算嘴型参数(仅对支持嘴型变化的模型)
|
||||
if (this.currentModelName && this.modelConfig[this.currentModelName]?.mouthFormParam) {
|
||||
const config = this.modelConfig[this.currentModelName];
|
||||
const formAmplitude = config.mouthFormAmplitude || 0.5;
|
||||
const form2Amplitude = config.mouthForm2Amplitude || 0;
|
||||
|
||||
// 嘴型随音量变化:
|
||||
// 低音量:嘴型偏"一"字(负值)
|
||||
// 高音量:嘴型偏"o"字(正值)
|
||||
// 音量=0时:嘴型=0(自然状态)
|
||||
mouthForm = (normalizedVolume - 0.5) * 2 * formAmplitude;
|
||||
mouthForm = Math.max(-formAmplitude, Math.min(formAmplitude, mouthForm));
|
||||
|
||||
// 第二嘴型参数(natori特有)
|
||||
if (config.mouthForm2Param) {
|
||||
mouthForm2 = (normalizedVolume - 0.3) * 2 * form2Amplitude;
|
||||
mouthForm2 = Math.max(-form2Amplitude, Math.min(form2Amplitude, mouthForm2));
|
||||
}
|
||||
}
|
||||
|
||||
// 调试日志:输出嘴部参数
|
||||
console.log(`[Live2D] 模型: ${this.currentModelName || 'unknown'}, 音量: ${average?.toFixed(0)}, OpenY: ${mouthOpenY.toFixed(3)}, Form: ${mouthForm.toFixed(3)}, Form2: ${mouthForm2.toFixed(3)}`);
|
||||
}
|
||||
coreModel.setParameterValueById(this.mouthParam, mouthValue);
|
||||
|
||||
// 设置嘴部开合参数
|
||||
coreModel.setParameterValueById(this.mouthParam, mouthOpenY);
|
||||
|
||||
// 设置嘴型参数(仅对支持嘴型变化的模型)
|
||||
if (this.currentModelName && this.modelConfig[this.currentModelName]?.mouthFormParam) {
|
||||
const config = this.modelConfig[this.currentModelName];
|
||||
const formParam = config.mouthFormParam;
|
||||
coreModel.setParameterValueById(formParam, mouthForm);
|
||||
|
||||
// 设置第二嘴型参数(natori特有)
|
||||
if (config.mouthForm2Param) {
|
||||
coreModel.setParameterValueById(config.mouthForm2Param, mouthForm2);
|
||||
}
|
||||
}
|
||||
|
||||
coreModel.update();
|
||||
}
|
||||
this.mouthAnimationId = requestAnimationFrame(() => this.animateMouth());
|
||||
@@ -473,12 +586,129 @@ class Live2DManager {
|
||||
motion(name) {
|
||||
try {
|
||||
if (!this.live2dModel) return;
|
||||
this.live2dModel.motion(name);
|
||||
|
||||
// 根据当前模型获取对应的动作名称
|
||||
let actualMotionName = name;
|
||||
if (this.currentModelName && this.modelConfig[this.currentModelName]) {
|
||||
const motionMap = this.modelConfig[this.currentModelName].motionMap;
|
||||
actualMotionName = motionMap[name] || name;
|
||||
}
|
||||
|
||||
this.live2dModel.motion(actualMotionName);
|
||||
} catch (error) {
|
||||
console.error('触发动作失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模型交互事件
|
||||
*/
|
||||
setupModelInteractions() {
|
||||
if (!this.live2dModel) return;
|
||||
|
||||
this.live2dModel.interactive = true;
|
||||
|
||||
this.live2dModel.on('doublehit', (args) => {
|
||||
const area = Array.isArray(args) ? args[0] : args;
|
||||
|
||||
if (area === 'Body') {
|
||||
this.motion('Flick@Body');
|
||||
} else if (area === 'Head' || area === 'Face') {
|
||||
this.motion('Flick');
|
||||
}
|
||||
|
||||
const app = window.chatApp;
|
||||
const payload = JSON.stringify({ type: 'live2d', event: 'doublehit', area });
|
||||
if (app && app.dataChannel && app.dataChannel.readyState === 'open') {
|
||||
app.dataChannel.send(payload);
|
||||
}
|
||||
});
|
||||
|
||||
this.live2dModel.on('singlehit', (args) => {
|
||||
const area = Array.isArray(args) ? args[0] : args;
|
||||
|
||||
if (area === 'Body') {
|
||||
this.motion('Tap@Body');
|
||||
} else if (area === 'Head' || area === 'Face') {
|
||||
this.motion('Tap');
|
||||
}
|
||||
|
||||
const app = window.chatApp;
|
||||
const payload = JSON.stringify({ type: 'live2d', event: 'singlehit', area });
|
||||
if (app && app.dataChannel && app.dataChannel.readyState === 'open') {
|
||||
app.dataChannel.send(payload);
|
||||
}
|
||||
});
|
||||
|
||||
this.live2dModel.on('swipe', (args) => {
|
||||
const area = Array.isArray(args) ? args[0] : args;
|
||||
const dir = Array.isArray(args) ? args[1] : undefined;
|
||||
|
||||
if (area === 'Body') {
|
||||
if (dir === 'up') {
|
||||
this.motion('FlickUp');
|
||||
} else if (dir === 'down') {
|
||||
this.motion('FlickDown');
|
||||
}
|
||||
}
|
||||
|
||||
const app = window.chatApp;
|
||||
const payload = JSON.stringify({ type: 'live2d', event: 'swipe', area, dir });
|
||||
if (app && app.dataChannel && app.dataChannel.readyState === 'open') {
|
||||
app.dataChannel.send(payload);
|
||||
}
|
||||
});
|
||||
|
||||
this.live2dModel.on('pointerdown', (event) => {
|
||||
try {
|
||||
const global = event.data.global;
|
||||
const bounds = this.live2dModel.getBounds();
|
||||
if (!bounds || !bounds.contains(global.x, global.y)) return;
|
||||
|
||||
const relX = (global.x - bounds.x) / (bounds.width || 1);
|
||||
const relY = (global.y - bounds.y) / (bounds.height || 1);
|
||||
let area = '';
|
||||
|
||||
if (relX >= 0.4 && relX <= 0.6) {
|
||||
if (relY <= 0.15) {
|
||||
area = 'Head';
|
||||
} else if (relY >= 0.7) {
|
||||
area = 'Body';
|
||||
}
|
||||
}
|
||||
|
||||
if (!area) return;
|
||||
|
||||
const now = Date.now();
|
||||
const dt = now - (this._lastClickTime || 0);
|
||||
const dx = global.x - (this._lastClickPos?.x || 0);
|
||||
const dy = global.y - (this._lastClickPos?.y || 0);
|
||||
const dist = Math.hypot(dx, dy);
|
||||
|
||||
if (this._lastClickTime && dt <= this._doubleClickMs && dist <= this._doubleClickDist) {
|
||||
if (this._singleClickTimer) {
|
||||
clearTimeout(this._singleClickTimer);
|
||||
this._singleClickTimer = null;
|
||||
}
|
||||
|
||||
this.live2dModel.emit('doublehit', area);
|
||||
this._lastClickTime = null;
|
||||
this._lastClickPos = null;
|
||||
} else {
|
||||
this._lastClickTime = now;
|
||||
this._lastClickPos = { x: global.x, y: global.y };
|
||||
|
||||
this._singleClickTimer = setTimeout(() => {
|
||||
this._singleClickTimer = null;
|
||||
this.live2dModel.emit('singlehit', area);
|
||||
}, this._doubleClickMs);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('pointerdown 处理出错:', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
@@ -500,6 +730,94 @@ class Live2DManager {
|
||||
}
|
||||
this.live2dModel = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换 Live2D 模型
|
||||
* @param {string} modelName - 模型目录名称,如 'hiyori_pro_zh'、'natori_pro_zh'
|
||||
* @returns {Promise<boolean>} - 切换是否成功
|
||||
*/
|
||||
async switchModel(modelName) {
|
||||
try {
|
||||
// 获取模型文件名映射
|
||||
const modelFileMap = {
|
||||
'hiyori_pro_zh': 'hiyori_pro_t11.model3.json',
|
||||
'natori_pro_zh': 'natori_pro_t06.model3.json',
|
||||
'chitose': 'chitose.model3.json',
|
||||
'haru_greeter_pro_jp': 'haru_greeter_t05.model3.json'
|
||||
};
|
||||
|
||||
const modelFileName = modelFileMap[modelName];
|
||||
if (!modelFileName) {
|
||||
console.error('未知的模型名称:', modelName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取基础路径
|
||||
const currentPath = window.location.pathname;
|
||||
const lastSlashIndex = currentPath.lastIndexOf('/');
|
||||
const basePath = currentPath.substring(0, lastSlashIndex + 1);
|
||||
const modelPath = basePath + 'resources/' + modelName + '/runtime/' + modelFileName;
|
||||
|
||||
// 如果已存在模型,先移除
|
||||
if (this.live2dModel) {
|
||||
this.live2dApp.stage.removeChild(this.live2dModel);
|
||||
this.live2dModel.destroy();
|
||||
this.live2dModel = null;
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
const app = window.chatApp;
|
||||
if (app) {
|
||||
app.setModelLoadingStatus(true);
|
||||
}
|
||||
|
||||
// 加载新模型
|
||||
this.live2dModel = await PIXI.live2d.Live2DModel.from(modelPath);
|
||||
this.live2dApp.stage.addChild(this.live2dModel);
|
||||
|
||||
// 设置模型属性
|
||||
this.live2dModel.scale.set(0.33);
|
||||
this.live2dModel.x = (window.innerWidth - this.live2dModel.width) * 0.5;
|
||||
this.live2dModel.y = -50;
|
||||
|
||||
// 重新绑定交互事件
|
||||
this.setupModelInteractions();
|
||||
|
||||
// 隐藏加载状态
|
||||
if (app) {
|
||||
app.setModelLoadingStatus(false);
|
||||
}
|
||||
|
||||
// 保存当前模型名称
|
||||
this.currentModelName = modelName;
|
||||
|
||||
// 设置模型特定的嘴部参数名
|
||||
if (this.modelConfig[modelName]) {
|
||||
this.mouthParam = this.modelConfig[modelName].mouthParam || 'ParamMouthOpenY';
|
||||
}
|
||||
|
||||
// 保存到 localStorage
|
||||
localStorage.setItem('live2dModel', modelName);
|
||||
|
||||
// 更新下拉框显示
|
||||
const modelSelect = document.getElementById('live2dModelSelect');
|
||||
if (modelSelect) {
|
||||
modelSelect.value = modelName;
|
||||
}
|
||||
|
||||
console.log('模型切换成功:', modelName);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('切换模型失败:', error);
|
||||
const app = window.chatApp;
|
||||
if (app) {
|
||||
app.setModelLoadingStatus(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 导出全局实例
|
||||
|
||||
@@ -11,8 +11,9 @@ class UIController {
|
||||
this.visualizerCanvas = null;
|
||||
this.visualizerContext = null;
|
||||
this.audioStatsTimer = null;
|
||||
this.currentBackgroundIndex = 0;
|
||||
this.currentBackgroundIndex = localStorage.getItem('backgroundIndex') ? parseInt(localStorage.getItem('backgroundIndex')) : 0;
|
||||
this.backgroundImages = ['1.png', '2.png', '3.png'];
|
||||
this.dialBtnDisabled = false;
|
||||
|
||||
// Bind methods
|
||||
this.init = this.init.bind(this);
|
||||
@@ -20,6 +21,7 @@ class UIController {
|
||||
this.updateDialButton = this.updateDialButton.bind(this);
|
||||
this.addChatMessage = this.addChatMessage.bind(this);
|
||||
this.switchBackground = this.switchBackground.bind(this);
|
||||
this.switchLive2DModel = this.switchLive2DModel.bind(this);
|
||||
this.showModal = this.showModal.bind(this);
|
||||
this.hideModal = this.hideModal.bind(this);
|
||||
this.switchTab = this.switchTab.bind(this);
|
||||
@@ -51,6 +53,12 @@ class UIController {
|
||||
|
||||
// Initialize status display
|
||||
this.updateConnectionUI(false);
|
||||
// Apply saved background
|
||||
const backgroundContainer = document.querySelector('.background-container');
|
||||
if (backgroundContainer) {
|
||||
backgroundContainer.style.backgroundImage = `url('./images/${this.backgroundImages[this.currentBackgroundIndex]}')`;
|
||||
}
|
||||
|
||||
this.updateDialButton(false);
|
||||
|
||||
console.log('UIController init completed');
|
||||
@@ -82,10 +90,25 @@ class UIController {
|
||||
backgroundBtn.addEventListener('click', this.switchBackground);
|
||||
}
|
||||
|
||||
// Model select change event
|
||||
const modelSelect = document.getElementById('live2dModelSelect');
|
||||
if (modelSelect) {
|
||||
modelSelect.addEventListener('change', () => {
|
||||
this.switchLive2DModel();
|
||||
});
|
||||
}
|
||||
|
||||
// Dial button
|
||||
const dialBtn = document.getElementById('dialBtn');
|
||||
if (dialBtn) {
|
||||
dialBtn.addEventListener('click', () => {
|
||||
dialBtn.disabled = true;
|
||||
this.dialBtnDisabled = true;
|
||||
setTimeout(() => {
|
||||
dialBtn.disabled = false;
|
||||
this.dialBtnDisabled = false;
|
||||
}, 3000);
|
||||
|
||||
const wsHandler = getWebSocketHandler();
|
||||
const isConnected = wsHandler.isConnected();
|
||||
|
||||
@@ -110,26 +133,80 @@ class UIController {
|
||||
});
|
||||
}
|
||||
|
||||
// Camera button
|
||||
const cameraBtn = document.getElementById('cameraBtn');
|
||||
let cameraTimer = null;
|
||||
if (cameraBtn) {
|
||||
cameraBtn.addEventListener('click', () => {
|
||||
if (cameraTimer) {
|
||||
clearTimeout(cameraTimer);
|
||||
cameraTimer = null;
|
||||
}
|
||||
cameraTimer = setTimeout(() => {
|
||||
const cameraContainer = document.getElementById('cameraContainer');
|
||||
if (!cameraContainer) {
|
||||
log('摄像头容器不存在', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const isActive = cameraContainer.classList.contains('active');
|
||||
if (isActive) {
|
||||
// 关闭摄像头
|
||||
if (typeof window.stopCamera === 'function') {
|
||||
window.stopCamera();
|
||||
}
|
||||
cameraContainer.classList.remove('active');
|
||||
cameraBtn.classList.remove('camera-active');
|
||||
cameraBtn.querySelector('.btn-text').textContent = '摄像头';
|
||||
log('摄像头已关闭', 'info');
|
||||
} else {
|
||||
// 打开摄像头
|
||||
if (typeof window.startCamera === 'function') {
|
||||
window.startCamera().then(success => {
|
||||
if (success) {
|
||||
cameraBtn.classList.add('camera-active');
|
||||
cameraBtn.querySelector('.btn-text').textContent = '关闭';
|
||||
} else {
|
||||
this.addChatMessage('⚠️ 摄像头启动失败,请检查浏览器权限', false);
|
||||
}
|
||||
}).catch(error => {
|
||||
log(`启动摄像头异常: ${error.message}`, 'error');
|
||||
});
|
||||
} else {
|
||||
log('startCamera函数未定义', 'warning');
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
// Record button
|
||||
const recordBtn = document.getElementById('recordBtn');
|
||||
if (recordBtn) {
|
||||
let recordTimer = null;
|
||||
recordBtn.addEventListener('click', () => {
|
||||
const audioRecorder = getAudioRecorder();
|
||||
if (audioRecorder.isRecording) {
|
||||
audioRecorder.stop();
|
||||
// Restore record button to normal state
|
||||
recordBtn.classList.remove('recording');
|
||||
recordBtn.querySelector('.btn-text').textContent = '录音';
|
||||
} else {
|
||||
// Update button state to recording
|
||||
recordBtn.classList.add('recording');
|
||||
recordBtn.querySelector('.btn-text').textContent = '录音中';
|
||||
|
||||
// Start recording, update button state after delay
|
||||
setTimeout(() => {
|
||||
audioRecorder.start();
|
||||
}, 100);
|
||||
if (recordTimer) {
|
||||
clearTimeout(recordTimer);
|
||||
recordTimer = null;
|
||||
}
|
||||
recordTimer = setTimeout(() => {
|
||||
const audioRecorder = getAudioRecorder();
|
||||
if (audioRecorder.isRecording) {
|
||||
audioRecorder.stop();
|
||||
// Restore record button to normal state
|
||||
recordBtn.classList.remove('recording');
|
||||
recordBtn.querySelector('.btn-text').textContent = '录音';
|
||||
} else {
|
||||
// Update button state to recording
|
||||
recordBtn.classList.add('recording');
|
||||
recordBtn.querySelector('.btn-text').textContent = '录音中';
|
||||
|
||||
// Start recording, update button state after delay
|
||||
setTimeout(() => {
|
||||
audioRecorder.start();
|
||||
}, 100);
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -220,6 +297,7 @@ class UIController {
|
||||
updateDialButton(isConnected) {
|
||||
const dialBtn = document.getElementById('dialBtn');
|
||||
const recordBtn = document.getElementById('recordBtn');
|
||||
const cameraBtn = document.getElementById('cameraBtn');
|
||||
|
||||
if (dialBtn) {
|
||||
if (isConnected) {
|
||||
@@ -239,6 +317,33 @@ class UIController {
|
||||
}
|
||||
}
|
||||
|
||||
// Update camera button state - reset to default when disconnected
|
||||
if (cameraBtn && !isConnected) {
|
||||
const cameraContainer = document.getElementById('cameraContainer');
|
||||
if (cameraContainer && cameraContainer.classList.contains('active')) {
|
||||
cameraContainer.classList.remove('active');
|
||||
}
|
||||
cameraBtn.classList.remove('camera-active');
|
||||
cameraBtn.querySelector('.btn-text').textContent = '摄像头';
|
||||
cameraBtn.disabled = true;
|
||||
cameraBtn.title = '请先连接服务器';
|
||||
// 关闭摄像头
|
||||
if (typeof window.stopCamera === 'function') {
|
||||
window.stopCamera();
|
||||
}
|
||||
}
|
||||
|
||||
// Update camera button state - enable when connected and camera is available
|
||||
if (cameraBtn && isConnected) {
|
||||
if (window.cameraAvailable) {
|
||||
cameraBtn.disabled = false;
|
||||
cameraBtn.title = '打开/关闭摄像头';
|
||||
} else {
|
||||
cameraBtn.disabled = true;
|
||||
cameraBtn.title = '请先绑定验证码';
|
||||
}
|
||||
}
|
||||
|
||||
// Update record button state
|
||||
if (recordBtn) {
|
||||
const microphoneAvailable = window.microphoneAvailable !== false;
|
||||
@@ -324,6 +429,36 @@ class UIController {
|
||||
if (backgroundContainer) {
|
||||
backgroundContainer.style.backgroundImage = `url('./images/${this.backgroundImages[this.currentBackgroundIndex]}')`;
|
||||
}
|
||||
localStorage.setItem('backgroundIndex', this.currentBackgroundIndex);
|
||||
}
|
||||
|
||||
// Switch Live2D model
|
||||
switchLive2DModel() {
|
||||
const modelSelect = document.getElementById('live2dModelSelect');
|
||||
if (!modelSelect) {
|
||||
console.error('模型选择下拉框不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedModel = modelSelect.value;
|
||||
const app = window.chatApp;
|
||||
|
||||
if (app && app.live2dManager) {
|
||||
app.live2dManager.switchModel(selectedModel)
|
||||
.then(success => {
|
||||
if (success) {
|
||||
this.addChatMessage(`已切换到模型: ${selectedModel}`, false);
|
||||
} else {
|
||||
this.addChatMessage('模型切换失败', false);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('模型切换错误:', error);
|
||||
this.addChatMessage('模型切换出错', false);
|
||||
});
|
||||
} else {
|
||||
this.addChatMessage('Live2D管理器未初始化', false);
|
||||
}
|
||||
}
|
||||
|
||||
// Show modal
|
||||
@@ -379,6 +514,22 @@ class UIController {
|
||||
recordBtn.click();
|
||||
}
|
||||
}
|
||||
// Start camera only if camera is available (bound with verification code)
|
||||
if (window.cameraAvailable && typeof window.startCamera === 'function') {
|
||||
window.startCamera().then(success => {
|
||||
if (success) {
|
||||
const cameraBtn = document.getElementById('cameraBtn');
|
||||
if (cameraBtn) {
|
||||
cameraBtn.classList.add('camera-active');
|
||||
cameraBtn.querySelector('.btn-text').textContent = '关闭';
|
||||
}
|
||||
} else {
|
||||
this.addChatMessage('⚠️ 摄像头启动失败,可能被浏览器拒绝', false);
|
||||
}
|
||||
}).catch(error => {
|
||||
log(`启动摄像头异常: ${error.message}`, 'error');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle connect button click
|
||||
@@ -468,13 +619,14 @@ class UIController {
|
||||
// Update dial button state
|
||||
const dialBtn = document.getElementById('dialBtn');
|
||||
if (dialBtn) {
|
||||
dialBtn.disabled = false;
|
||||
if (!this.dialBtnDisabled) {
|
||||
dialBtn.disabled = false;
|
||||
}
|
||||
dialBtn.querySelector('.btn-text').textContent = '挂断';
|
||||
dialBtn.classList.add('dial-active');
|
||||
}
|
||||
|
||||
this.hideModal('settingsModal');
|
||||
|
||||
} else {
|
||||
throw new Error('OTA连接失败');
|
||||
}
|
||||
@@ -495,7 +647,9 @@ class UIController {
|
||||
// Restore dial button state
|
||||
const dialBtn = document.getElementById('dialBtn');
|
||||
if (dialBtn) {
|
||||
dialBtn.disabled = false;
|
||||
if (!this.dialBtnDisabled) {
|
||||
dialBtn.disabled = false;
|
||||
}
|
||||
dialBtn.querySelector('.btn-text').textContent = '拨号';
|
||||
dialBtn.classList.remove('dial-active');
|
||||
console.log('Dial button state restored successfully');
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 2.4 MiB |
@@ -0,0 +1,79 @@
|
||||
|
||||
============================================================
|
||||
|
||||
示例模型
|
||||
名执 尽 - PRO
|
||||
|
||||
============================================================
|
||||
|
||||
该示例可用于学习商用的游戏和App中常用到的手臂切换等难度较高的制作方法。
|
||||
|
||||
该模型角色由画师先崎真琴设计。
|
||||
模型的手臂通过渐变实现不同部件间的切换,这种制作方法经常应用于商用的游戏和App中。
|
||||
通过该模型,可以学习部件的切换来实现更丰富的动画。
|
||||
|
||||
另外,可以通过Cubism3 Viewer (for OW)来查看嵌入式文件组的动画,了解完整的工作流程中使用到的数据结构。
|
||||
|
||||
|
||||
------------------------------
|
||||
素材使用许可
|
||||
------------------------------
|
||||
|
||||
普通用户以及小规模企业在同意授权协议的情况下可用于商业用途。
|
||||
中/大规模的企业只能用于非公开的内部试用。
|
||||
在使用该素材时,请确认以下的【无偿提供素材使用授权协议】中的“授权类型”、“Live2D原创角色”等的相关内容,
|
||||
并必须接受【Live2D Cubism 示例模型的使用授权要求】中的利用条件。
|
||||
|
||||
有关许可证的更多信息,请参阅以下页面。
|
||||
https://www.live2d.com/zh-CHS/download/sample-data/
|
||||
|
||||
|
||||
------------------------------
|
||||
创作者
|
||||
------------------------------
|
||||
|
||||
插画:先崎 真琴【http://senzakimakoto.com/】
|
||||
配音:小野友树【https://web.onoyuki.com/】
|
||||
(※配音数据的发布为限定发布,已于2018/06/04结束。)
|
||||
模型:Live2D
|
||||
|
||||
|
||||
------------------------------
|
||||
素材内容
|
||||
------------------------------
|
||||
|
||||
模型文件(cmo3) ※包含物理模拟的设定
|
||||
表情动画文件(can3)
|
||||
基本动画文件(can3)
|
||||
嵌入文件组(runtime文件夹)
|
||||
・模型数据(moc3)
|
||||
・表情数据(exp3.json)
|
||||
・动作数据(motion3.json)
|
||||
・模型设定文件(model3.json)
|
||||
・姿势设定文件(pose3.json)
|
||||
・物理模拟设定文件(physics3.json)
|
||||
・辅助显示的文件(cdi3.json)
|
||||
|
||||
|
||||
------------------------------
|
||||
更新记录
|
||||
------------------------------
|
||||
※配音数据的发布已于2018/06/04结束。
|
||||
|
||||
【cmo3】
|
||||
|
||||
natori_pro_t06.cmo3
|
||||
2021年6月10日 公开
|
||||
|
||||
|
||||
【can3】
|
||||
|
||||
natori_pro_exp_t03.can3
|
||||
2021年6月10日 公开
|
||||
|
||||
|
||||
【can3】
|
||||
|
||||
natori_pro_motions_t03.can3
|
||||
2021年6月10日 公开
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"Type": "Live2D Expression",
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLForm",
|
||||
"Value": -2,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRForm",
|
||||
"Value": -2,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"Value": 0.3,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"Value": 0.3,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Value": -0.4,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Value": -0.4,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamTeethOn",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGlassUD",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassWhite",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlight",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlightMove",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"Type": "Live2D Expression",
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamTeethOn",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGlassUD",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassWhite",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlight",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlightMove",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"Type": "Live2D Expression",
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamTeethOn",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGlassUD",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassWhite",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlight",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlightMove",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"Type": "Live2D Expression",
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLForm",
|
||||
"Value": -2,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRForm",
|
||||
"Value": -2,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamTeethOn",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGlassUD",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassWhite",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlight",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlightMove",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"Type": "Live2D Expression",
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm2",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamTeethOn",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGlassUD",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassWhite",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlight",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlightMove",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"Type": "Live2D Expression",
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Value": 0.3,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"Value": 0.3,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallForm",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"Value": 0.2,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"Value": 0.2,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"Value": -0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"Value": -0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Value": 0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Value": 0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamTeethOn",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGlassUD",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassWhite",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlight",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlightMove",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"Type": "Live2D Expression",
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLForm",
|
||||
"Value": 3,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRForm",
|
||||
"Value": 3,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"Value": -0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"Value": -0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm2",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm2",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamTeethOn",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGlassUD",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassWhite",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlight",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlightMove",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"Type": "Live2D Expression",
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLForm",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRForm",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"Value": 0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"Value": 0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Value": 0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Value": 0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm2",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm2",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamTeethOn",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGlassUD",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassWhite",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlight",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlightMove",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"Type": "Live2D Expression",
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamTeethOn",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGlassUD",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassWhite",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlight",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlightMove",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"Type": "Live2D Expression",
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Value": 0.2,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"Value": 0.2,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"Value": 0.2,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"Value": 0.2,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"Value": -0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"Value": -0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Value": 0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Value": 0.1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamTeethOn",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGlassUD",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassWhite",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlight",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlightMove",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"Type": "Live2D Expression",
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"Value": -1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallForm",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"Value": 1,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm2",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamTeethOn",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGlassUD",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassWhite",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlight",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlightMove",
|
||||
"Value": 0,
|
||||
"Blend": "Add"
|
||||
}
|
||||
]
|
||||
}
|
||||
+1432
File diff suppressed because it is too large
Load Diff
+1971
File diff suppressed because it is too large
Load Diff
+2182
File diff suppressed because it is too large
Load Diff
+2596
File diff suppressed because it is too large
Load Diff
+2316
File diff suppressed because it is too large
Load Diff
+2345
File diff suppressed because it is too large
Load Diff
+2082
File diff suppressed because it is too large
Load Diff
+1907
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"Type": "Live2D Pose",
|
||||
"FadeInTime": 0.2,
|
||||
"Groups": [
|
||||
[
|
||||
{
|
||||
"Id": "PartArmAL",
|
||||
"Link": []
|
||||
},
|
||||
{
|
||||
"Id": "PartArmCL",
|
||||
"Link": []
|
||||
},
|
||||
{
|
||||
"Id": "PartArmDL",
|
||||
"Link": []
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"Id": "PartArmAR",
|
||||
"Link": []
|
||||
},
|
||||
{
|
||||
"Id": "PartArmBR",
|
||||
"Link": []
|
||||
},
|
||||
{
|
||||
"Id": "PartArmER",
|
||||
"Link": []
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"Id": "PartWatchA",
|
||||
"Link": []
|
||||
},
|
||||
{
|
||||
"Id": "PartWatchB",
|
||||
"Link": []
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 6.6 MiB |
@@ -0,0 +1,711 @@
|
||||
{
|
||||
"Version": 3,
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamAngleX",
|
||||
"GroupId": "",
|
||||
"Name": "角度 X"
|
||||
},
|
||||
{
|
||||
"Id": "ParamAngleY",
|
||||
"GroupId": "",
|
||||
"Name": "角度 Y"
|
||||
},
|
||||
{
|
||||
"Id": "ParamAngleZ",
|
||||
"GroupId": "",
|
||||
"Name": "角度 Z"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "左眼 开闭"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "左眼 微笑"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLForm",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "左眼 变形"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "右眼 开闭"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "右眼 微笑"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRForm",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "右眼 变形"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallX",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "眼珠 X"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallY",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "眼珠 Y"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallForm",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "眼珠 缩放"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "左眉 上下"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "右眉 上下"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "左眉 左右"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "右眉 左右"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "左眉 角度"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "右眉 角度"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "左眉 变形"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm2",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "左眉 变形2"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "右眉 变形"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm2",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "右眉 变形2"
|
||||
},
|
||||
{
|
||||
"Id": "ParamMouthForm",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "嘴 变形"
|
||||
},
|
||||
{
|
||||
"Id": "ParamMouthOpenY",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "嘴 开闭"
|
||||
},
|
||||
{
|
||||
"Id": "ParamMouthForm2",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "嘴 变形2"
|
||||
},
|
||||
{
|
||||
"Id": "ParamTeethOn",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "牙齿的显示"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "害羞"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGlassUD",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "眼镜 上下"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassWhite",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "镜片 白"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlight",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "镜片 反光显示"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGrassHighlightMove",
|
||||
"GroupId": "ParamGroupExpression",
|
||||
"Name": "镜片 反光移动"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBodyAngleX",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "身体的旋转 X"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBodyAngleY",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "身体的旋转 Y"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBodyAngleZ",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "身体的旋转 Z"
|
||||
},
|
||||
{
|
||||
"Id": "ParamWaistAngleZ",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "腰的旋转 Z"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBodyPosition",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "身体的前后"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBreath",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "呼吸"
|
||||
},
|
||||
{
|
||||
"Id": "ParamLeftShoulderUp",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "左肩的上下"
|
||||
},
|
||||
{
|
||||
"Id": "ParamRightShoulderUp",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "右肩的上下"
|
||||
},
|
||||
{
|
||||
"Id": "ParamAllX",
|
||||
"GroupId": "ParamGroup",
|
||||
"Name": "整体的移动 X"
|
||||
},
|
||||
{
|
||||
"Id": "ParamAllY",
|
||||
"GroupId": "ParamGroup",
|
||||
"Name": "整体的移动 Y"
|
||||
},
|
||||
{
|
||||
"Id": "ParamAllRotate",
|
||||
"GroupId": "ParamGroup",
|
||||
"Name": "整体的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHairFront",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "头发摇动 前"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHairSide",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "头发摇动 侧"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHairBack",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "头发摇动 后"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHairFrontFuwa",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "前发 蓬松"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHairSideFuwa",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "侧发 蓬松"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHairBackFuwa",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "后发 蓬松"
|
||||
},
|
||||
{
|
||||
"Id": "ParamJacket",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "燕尾的摇动"
|
||||
},
|
||||
{
|
||||
"Id": "ParamChainWaist",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "表链A的摇动"
|
||||
},
|
||||
{
|
||||
"Id": "ParamWatchSwingA1",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "怀表A 摇动1"
|
||||
},
|
||||
{
|
||||
"Id": "ParamWatchSwingA2",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "怀表A 摇动2"
|
||||
},
|
||||
{
|
||||
"Id": "ParamWatchBChain",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "怀表B 表链的摇动"
|
||||
},
|
||||
{
|
||||
"Id": "ParamWatchAX",
|
||||
"GroupId": "ParamGroup8",
|
||||
"Name": "怀表A 横向旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamWatchBSwitch",
|
||||
"GroupId": "ParamGroup9",
|
||||
"Name": "怀表B 开关"
|
||||
},
|
||||
{
|
||||
"Id": "ParamWatchBOpen",
|
||||
"GroupId": "ParamGroup9",
|
||||
"Name": "怀表B 表盖的开闭"
|
||||
},
|
||||
{
|
||||
"Id": "ParamWatchBOpen2",
|
||||
"GroupId": "ParamGroup9",
|
||||
"Name": "怀表B 表盘的开闭"
|
||||
},
|
||||
{
|
||||
"Id": "ParamWatchBX",
|
||||
"GroupId": "ParamGroup9",
|
||||
"Name": "怀表B 横向旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamWatchBRoll",
|
||||
"GroupId": "ParamGroup9",
|
||||
"Name": "怀表B 旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamWatchBLR",
|
||||
"GroupId": "ParamGroup9",
|
||||
"Name": "怀表B 左右"
|
||||
},
|
||||
{
|
||||
"Id": "ParamWatchBUD",
|
||||
"GroupId": "ParamGroup9",
|
||||
"Name": "怀表B 上下"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmAL01",
|
||||
"GroupId": "ParamGroup3",
|
||||
"Name": "左手臂A 肩"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmAL02",
|
||||
"GroupId": "ParamGroup3",
|
||||
"Name": "左肩A 手肘旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmAL03",
|
||||
"GroupId": "ParamGroup3",
|
||||
"Name": "左手臂A 手腕"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmAL04",
|
||||
"GroupId": "ParamGroup3",
|
||||
"Name": "左手臂A 前臂的前后"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmAR01",
|
||||
"GroupId": "ParamGroup2",
|
||||
"Name": "右手臂A 肩的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmAR02",
|
||||
"GroupId": "ParamGroup2",
|
||||
"Name": "右手臂A 手肘的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmAR03",
|
||||
"GroupId": "ParamGroup2",
|
||||
"Name": "右手臂A 手腕的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmAR04",
|
||||
"GroupId": "ParamGroup2",
|
||||
"Name": "右手臂A 前臂的前后"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmBR01",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "右手臂B 肩的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmBR02",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "右手臂B 手肘的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmBR03",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "右手臂B 手腕的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmBRHand01",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "右手01 显示"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmBRHand01Roll",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "右手01 手指弯曲"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmBRHand05",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "右手05 显示"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmBRHand05Roll1",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "右手05 手指弯曲1"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmBRHand05Roll2",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "右手05 手指弯曲2"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmBRHand05Roll3",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "右手05 手指弯曲3"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmCR01",
|
||||
"GroupId": "ParamGroup5",
|
||||
"Name": "左手臂C 肩的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmCR02",
|
||||
"GroupId": "ParamGroup5",
|
||||
"Name": "左手臂C 手肘的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmCR03",
|
||||
"GroupId": "ParamGroup5",
|
||||
"Name": "左手臂C 手腕的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmCLHandRoll1",
|
||||
"GroupId": "ParamGroup5",
|
||||
"Name": "左手C 手指弯曲1"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmDL01",
|
||||
"GroupId": "ParamGroup6",
|
||||
"Name": "左手臂D 肩的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmDL02",
|
||||
"GroupId": "ParamGroup6",
|
||||
"Name": "左手臂D 手肘的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmDL03",
|
||||
"GroupId": "ParamGroup6",
|
||||
"Name": "左手臂D 手腕的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmDLHand03Roll",
|
||||
"GroupId": "ParamGroup6",
|
||||
"Name": "左手03 手指弯曲"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmER01",
|
||||
"GroupId": "ParamGroup7",
|
||||
"Name": "右手臂E 肩的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmER02",
|
||||
"GroupId": "ParamGroup7",
|
||||
"Name": "右手臂E 手肘的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmER03",
|
||||
"GroupId": "ParamGroup7",
|
||||
"Name": "右手臂E 手腕的旋转"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmER04",
|
||||
"GroupId": "ParamGroup7",
|
||||
"Name": "右手臂E 上臂的长度"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmERHand04",
|
||||
"GroupId": "ParamGroup7",
|
||||
"Name": "右手04 显示"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmERHand04Roll1",
|
||||
"GroupId": "ParamGroup7",
|
||||
"Name": "右手04 手指弯曲1"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmERHand04Roll2",
|
||||
"GroupId": "ParamGroup7",
|
||||
"Name": "右手04 手指弯曲2"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmERHand06",
|
||||
"GroupId": "ParamGroup7",
|
||||
"Name": "右手06 显示"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmERHand06Roll1",
|
||||
"GroupId": "ParamGroup7",
|
||||
"Name": "右手06 手指弯曲1"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmERHand06Roll2",
|
||||
"GroupId": "ParamGroup7",
|
||||
"Name": "右手06 手指弯曲2"
|
||||
}
|
||||
],
|
||||
"ParameterGroups": [
|
||||
{
|
||||
"Id": "ParamGroupExpression",
|
||||
"GroupId": "",
|
||||
"Name": "表情"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroupBody",
|
||||
"GroupId": "",
|
||||
"Name": "身体"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup",
|
||||
"GroupId": "",
|
||||
"Name": "整体移动"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroupSway",
|
||||
"GroupId": "",
|
||||
"Name": "摇动"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup8",
|
||||
"GroupId": "",
|
||||
"Name": "怀表A"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup9",
|
||||
"GroupId": "",
|
||||
"Name": "怀表B"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup3",
|
||||
"GroupId": "",
|
||||
"Name": "左手臂A"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup2",
|
||||
"GroupId": "",
|
||||
"Name": "右手臂A"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup4",
|
||||
"GroupId": "",
|
||||
"Name": "右手臂B"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup5",
|
||||
"GroupId": "",
|
||||
"Name": "左手臂C"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup6",
|
||||
"GroupId": "",
|
||||
"Name": "左手臂D"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup7",
|
||||
"GroupId": "",
|
||||
"Name": "右手臂E"
|
||||
}
|
||||
],
|
||||
"Parts": [
|
||||
{
|
||||
"Id": "PartCredit",
|
||||
"Name": "名牌"
|
||||
},
|
||||
{
|
||||
"Id": "PartCore",
|
||||
"Name": "CORE"
|
||||
},
|
||||
{
|
||||
"Id": "PartGlass",
|
||||
"Name": "眼镜"
|
||||
},
|
||||
{
|
||||
"Id": "PartWatchA",
|
||||
"Name": "怀表A"
|
||||
},
|
||||
{
|
||||
"Id": "PartWatchB",
|
||||
"Name": "怀表B"
|
||||
},
|
||||
{
|
||||
"Id": "PartHairFront",
|
||||
"Name": "前发"
|
||||
},
|
||||
{
|
||||
"Id": "PartHead",
|
||||
"Name": "头"
|
||||
},
|
||||
{
|
||||
"Id": "PartUpperBody",
|
||||
"Name": "上半身"
|
||||
},
|
||||
{
|
||||
"Id": "PartHairBack",
|
||||
"Name": "后发"
|
||||
},
|
||||
{
|
||||
"Id": "PartLowerBody",
|
||||
"Name": "下半身"
|
||||
},
|
||||
{
|
||||
"Id": "PartArmAL",
|
||||
"Name": "左手臂A"
|
||||
},
|
||||
{
|
||||
"Id": "PartArmAR",
|
||||
"Name": "右手臂A"
|
||||
},
|
||||
{
|
||||
"Id": "PartArmBR",
|
||||
"Name": "右手臂B"
|
||||
},
|
||||
{
|
||||
"Id": "PartArmCL",
|
||||
"Name": "左手臂C"
|
||||
},
|
||||
{
|
||||
"Id": "PartArmDL",
|
||||
"Name": "左手臂D"
|
||||
},
|
||||
{
|
||||
"Id": "PartArmER",
|
||||
"Name": "右手臂E"
|
||||
},
|
||||
{
|
||||
"Id": "PartEyeBlow",
|
||||
"Name": "眉毛"
|
||||
},
|
||||
{
|
||||
"Id": "PartEyeR",
|
||||
"Name": "右眼"
|
||||
},
|
||||
{
|
||||
"Id": "PartEyeL",
|
||||
"Name": "左眼"
|
||||
},
|
||||
{
|
||||
"Id": "PartHairLine",
|
||||
"Name": "发际线"
|
||||
},
|
||||
{
|
||||
"Id": "PartHairShadow",
|
||||
"Name": "头发阴影"
|
||||
},
|
||||
{
|
||||
"Id": "PartNose",
|
||||
"Name": "鼻子"
|
||||
},
|
||||
{
|
||||
"Id": "PartMouth",
|
||||
"Name": "嘴"
|
||||
},
|
||||
{
|
||||
"Id": "PartJacket",
|
||||
"Name": "燕尾服"
|
||||
},
|
||||
{
|
||||
"Id": "PartArmALFore",
|
||||
"Name": "左手臂A 前臂"
|
||||
},
|
||||
{
|
||||
"Id": "PartArmARFore",
|
||||
"Name": "右手臂A 前臂"
|
||||
},
|
||||
{
|
||||
"Id": "PartHand11",
|
||||
"Name": "手套_1"
|
||||
},
|
||||
{
|
||||
"Id": "PartHand51",
|
||||
"Name": "手套_5"
|
||||
},
|
||||
{
|
||||
"Id": "PartHand21",
|
||||
"Name": "手套_2"
|
||||
},
|
||||
{
|
||||
"Id": "PartHand31",
|
||||
"Name": "手套_3"
|
||||
},
|
||||
{
|
||||
"Id": "PartHand41",
|
||||
"Name": "手套_4"
|
||||
},
|
||||
{
|
||||
"Id": "PartHand61",
|
||||
"Name": "手套_6"
|
||||
}
|
||||
],
|
||||
"CombinedParameters": [
|
||||
[
|
||||
"ParamAngleX",
|
||||
"ParamAngleY"
|
||||
],
|
||||
[
|
||||
"ParamEyeBallX",
|
||||
"ParamEyeBallY"
|
||||
],
|
||||
[
|
||||
"ParamMouthForm",
|
||||
"ParamMouthOpenY"
|
||||
],
|
||||
[
|
||||
"ParamBodyAngleX",
|
||||
"ParamBodyAngleY"
|
||||
],
|
||||
[
|
||||
"ParamLeftShoulderUp",
|
||||
"ParamRightShoulderUp"
|
||||
],
|
||||
[
|
||||
"ParamAllX",
|
||||
"ParamAllY"
|
||||
],
|
||||
[
|
||||
"ParamWatchSwingA1",
|
||||
"ParamWatchSwingA2"
|
||||
],
|
||||
[
|
||||
"ParamWatchBLR",
|
||||
"ParamWatchBUD"
|
||||
]
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"Version": 3,
|
||||
"FileReferences": {
|
||||
"Moc": "natori_pro_t06.moc3",
|
||||
"Textures": [
|
||||
"natori_pro_t06.4096/texture_00.png"
|
||||
],
|
||||
"Physics": "natori_pro_t06.physics3.json",
|
||||
"Pose": "natori.pose3.json",
|
||||
"DisplayInfo": "natori_pro_t06.cdi3.json",
|
||||
"Expressions": [
|
||||
{
|
||||
"Name": "Angry",
|
||||
"File": "exp/Angry.exp3.json"
|
||||
},
|
||||
{
|
||||
"Name": "Blushing",
|
||||
"File": "exp/Blushing.exp3.json"
|
||||
},
|
||||
{
|
||||
"Name": "Normal",
|
||||
"File": "exp/Normal.exp3.json"
|
||||
},
|
||||
{
|
||||
"Name": "Sad",
|
||||
"File": "exp/Sad.exp3.json"
|
||||
},
|
||||
{
|
||||
"Name": "Smile",
|
||||
"File": "exp/Smile.exp3.json"
|
||||
},
|
||||
{
|
||||
"Name": "Surprised",
|
||||
"File": "exp/Surprised.exp3.json"
|
||||
},
|
||||
{
|
||||
"Name": "exp_01",
|
||||
"File": "exp/exp_01.exp3.json"
|
||||
},
|
||||
{
|
||||
"Name": "exp_02",
|
||||
"File": "exp/exp_02.exp3.json"
|
||||
},
|
||||
{
|
||||
"Name": "exp_03",
|
||||
"File": "exp/exp_03.exp3.json"
|
||||
},
|
||||
{
|
||||
"Name": "exp_04",
|
||||
"File": "exp/exp_04.exp3.json"
|
||||
},
|
||||
{
|
||||
"Name": "exp_05",
|
||||
"File": "exp/exp_05.exp3.json"
|
||||
}
|
||||
],
|
||||
"Motions": {
|
||||
"Idle": [
|
||||
{
|
||||
"File": "motions/mtn_00.motion3.json"
|
||||
},
|
||||
{
|
||||
"File": "motions/mtn_01.motion3.json"
|
||||
},
|
||||
{
|
||||
"File": "motions/mtn_02.motion3.json"
|
||||
}
|
||||
],
|
||||
"Tap": [
|
||||
{
|
||||
"File": "motions/mtn_03.motion3.json"
|
||||
}
|
||||
],
|
||||
"FlickUp@Head": [
|
||||
{
|
||||
"File": "motions/mtn_04.motion3.json"
|
||||
}
|
||||
],
|
||||
"Flick@Body": [
|
||||
{
|
||||
"File": "motions/mtn_05.motion3.json"
|
||||
}
|
||||
],
|
||||
"FlickDown@Body": [
|
||||
{
|
||||
"File": "motions/mtn_06.motion3.json"
|
||||
}
|
||||
],
|
||||
"Tap@Head": [
|
||||
{
|
||||
"File": "motions/mtn_07.motion3.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Groups": [
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Name": "LipSync",
|
||||
"Ids": [
|
||||
"ParamMouthOpenY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Name": "EyeBlink",
|
||||
"Ids": [
|
||||
"ParamEyeLOpen",
|
||||
"ParamEyeROpen"
|
||||
]
|
||||
}
|
||||
],
|
||||
"HitAreas": [
|
||||
{
|
||||
"Id": "HitAreaHead",
|
||||
"Name": ""
|
||||
},
|
||||
{
|
||||
"Id": "HitAreaBody",
|
||||
"Name": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
+966
@@ -0,0 +1,966 @@
|
||||
{
|
||||
"Version": 3,
|
||||
"Meta": {
|
||||
"PhysicsSettingCount": 11,
|
||||
"TotalInputCount": 34,
|
||||
"TotalOutputCount": 12,
|
||||
"VertexCount": 23,
|
||||
"EffectiveForces": {
|
||||
"Gravity": {
|
||||
"X": 0,
|
||||
"Y": -1
|
||||
},
|
||||
"Wind": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
}
|
||||
},
|
||||
"PhysicsDictionary": [
|
||||
{
|
||||
"Id": "PhysicsSetting1",
|
||||
"Name": "前髪 揺れ"
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting2",
|
||||
"Name": "横髪 揺れ"
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting3",
|
||||
"Name": "後ろ髪 揺れ"
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting4",
|
||||
"Name": "前髪ふわ"
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting5",
|
||||
"Name": "横髪ふわ"
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting6",
|
||||
"Name": "後ろ髪ふわ"
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting7",
|
||||
"Name": "燕尾揺れ"
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting8",
|
||||
"Name": "懐中時計腰 揺れ"
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting9",
|
||||
"Name": "腰のチェーン揺れ"
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting10",
|
||||
"Name": "懐中時計腰 横回転"
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting11",
|
||||
"Name": "懐中時計B チェーン揺れ"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PhysicsSettings": [
|
||||
{
|
||||
"Id": "PhysicsSetting1",
|
||||
"Input": [
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleX"
|
||||
},
|
||||
"Weight": 60,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleZ"
|
||||
},
|
||||
"Weight": 60,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleX"
|
||||
},
|
||||
"Weight": 40,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleZ"
|
||||
},
|
||||
"Weight": 40,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Output": [
|
||||
{
|
||||
"Destination": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamHairFront"
|
||||
},
|
||||
"VertexIndex": 1,
|
||||
"Scale": 1.824,
|
||||
"Weight": 100,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Vertices": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
},
|
||||
"Mobility": 1,
|
||||
"Delay": 1,
|
||||
"Acceleration": 1,
|
||||
"Radius": 0
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 12.7
|
||||
},
|
||||
"Mobility": 0.95,
|
||||
"Delay": 0.9,
|
||||
"Acceleration": 1,
|
||||
"Radius": 12.7
|
||||
}
|
||||
],
|
||||
"Normalization": {
|
||||
"Position": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
},
|
||||
"Angle": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting2",
|
||||
"Input": [
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleX"
|
||||
},
|
||||
"Weight": 60,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleZ"
|
||||
},
|
||||
"Weight": 60,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleX"
|
||||
},
|
||||
"Weight": 40,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleZ"
|
||||
},
|
||||
"Weight": 40,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Output": [
|
||||
{
|
||||
"Destination": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamHairSide"
|
||||
},
|
||||
"VertexIndex": 1,
|
||||
"Scale": 2,
|
||||
"Weight": 100,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Vertices": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
},
|
||||
"Mobility": 1,
|
||||
"Delay": 1,
|
||||
"Acceleration": 1,
|
||||
"Radius": 0
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 18.2
|
||||
},
|
||||
"Mobility": 0.95,
|
||||
"Delay": 0.9,
|
||||
"Acceleration": 1,
|
||||
"Radius": 18.2
|
||||
}
|
||||
],
|
||||
"Normalization": {
|
||||
"Position": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
},
|
||||
"Angle": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting3",
|
||||
"Input": [
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleX"
|
||||
},
|
||||
"Weight": 60,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleZ"
|
||||
},
|
||||
"Weight": 60,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleX"
|
||||
},
|
||||
"Weight": 40,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleZ"
|
||||
},
|
||||
"Weight": 40,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Output": [
|
||||
{
|
||||
"Destination": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamHairBack"
|
||||
},
|
||||
"VertexIndex": 1,
|
||||
"Scale": 2,
|
||||
"Weight": 100,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Vertices": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
},
|
||||
"Mobility": 1,
|
||||
"Delay": 1,
|
||||
"Acceleration": 1,
|
||||
"Radius": 0
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 14.3
|
||||
},
|
||||
"Mobility": 1,
|
||||
"Delay": 0.9,
|
||||
"Acceleration": 1.42,
|
||||
"Radius": 14.3
|
||||
}
|
||||
],
|
||||
"Normalization": {
|
||||
"Position": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
},
|
||||
"Angle": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting4",
|
||||
"Input": [
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleY"
|
||||
},
|
||||
"Weight": 35,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleY"
|
||||
},
|
||||
"Weight": 30,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyPosition"
|
||||
},
|
||||
"Weight": 35,
|
||||
"Type": "X",
|
||||
"Reflect": true
|
||||
}
|
||||
],
|
||||
"Output": [
|
||||
{
|
||||
"Destination": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamHairFrontFuwa"
|
||||
},
|
||||
"VertexIndex": 1,
|
||||
"Scale": 3,
|
||||
"Weight": 100,
|
||||
"Type": "Angle",
|
||||
"Reflect": true
|
||||
}
|
||||
],
|
||||
"Vertices": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
},
|
||||
"Mobility": 1,
|
||||
"Delay": 1,
|
||||
"Acceleration": 1,
|
||||
"Radius": 0
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 11.9
|
||||
},
|
||||
"Mobility": 0.79,
|
||||
"Delay": 0.9,
|
||||
"Acceleration": 1,
|
||||
"Radius": 11.9
|
||||
}
|
||||
],
|
||||
"Normalization": {
|
||||
"Position": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
},
|
||||
"Angle": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting5",
|
||||
"Input": [
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleY"
|
||||
},
|
||||
"Weight": 35,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleY"
|
||||
},
|
||||
"Weight": 30,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyPosition"
|
||||
},
|
||||
"Weight": 35,
|
||||
"Type": "X",
|
||||
"Reflect": true
|
||||
}
|
||||
],
|
||||
"Output": [
|
||||
{
|
||||
"Destination": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamHairSideFuwa"
|
||||
},
|
||||
"VertexIndex": 1,
|
||||
"Scale": 3.5,
|
||||
"Weight": 100,
|
||||
"Type": "Angle",
|
||||
"Reflect": true
|
||||
}
|
||||
],
|
||||
"Vertices": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
},
|
||||
"Mobility": 1,
|
||||
"Delay": 1,
|
||||
"Acceleration": 1,
|
||||
"Radius": 0
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 14.3
|
||||
},
|
||||
"Mobility": 0.79,
|
||||
"Delay": 0.9,
|
||||
"Acceleration": 1.1,
|
||||
"Radius": 14.3
|
||||
}
|
||||
],
|
||||
"Normalization": {
|
||||
"Position": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
},
|
||||
"Angle": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting6",
|
||||
"Input": [
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleY"
|
||||
},
|
||||
"Weight": 35,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleY"
|
||||
},
|
||||
"Weight": 30,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyPosition"
|
||||
},
|
||||
"Weight": 35,
|
||||
"Type": "X",
|
||||
"Reflect": true
|
||||
}
|
||||
],
|
||||
"Output": [
|
||||
{
|
||||
"Destination": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamHairBackFuwa"
|
||||
},
|
||||
"VertexIndex": 1,
|
||||
"Scale": 5,
|
||||
"Weight": 100,
|
||||
"Type": "Angle",
|
||||
"Reflect": true
|
||||
}
|
||||
],
|
||||
"Vertices": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
},
|
||||
"Mobility": 1,
|
||||
"Delay": 1,
|
||||
"Acceleration": 1,
|
||||
"Radius": 0
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 11.3
|
||||
},
|
||||
"Mobility": 0.79,
|
||||
"Delay": 0.9,
|
||||
"Acceleration": 1.16,
|
||||
"Radius": 11.3
|
||||
}
|
||||
],
|
||||
"Normalization": {
|
||||
"Position": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
},
|
||||
"Angle": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting7",
|
||||
"Input": [
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleX"
|
||||
},
|
||||
"Weight": 100,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleZ"
|
||||
},
|
||||
"Weight": 70,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamWaistAngleZ"
|
||||
},
|
||||
"Weight": 30,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Output": [
|
||||
{
|
||||
"Destination": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamJacket"
|
||||
},
|
||||
"VertexIndex": 1,
|
||||
"Scale": 1,
|
||||
"Weight": 100,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Vertices": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
},
|
||||
"Mobility": 1,
|
||||
"Delay": 1,
|
||||
"Acceleration": 1,
|
||||
"Radius": 0
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 31.9
|
||||
},
|
||||
"Mobility": 0.95,
|
||||
"Delay": 0.8,
|
||||
"Acceleration": 0.8,
|
||||
"Radius": 31.9
|
||||
}
|
||||
],
|
||||
"Normalization": {
|
||||
"Position": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
},
|
||||
"Angle": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting8",
|
||||
"Input": [
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleX"
|
||||
},
|
||||
"Weight": 100,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleZ"
|
||||
},
|
||||
"Weight": 70,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamWaistAngleZ"
|
||||
},
|
||||
"Weight": 30,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Output": [
|
||||
{
|
||||
"Destination": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamWatchSwingA1"
|
||||
},
|
||||
"VertexIndex": 1,
|
||||
"Scale": 1,
|
||||
"Weight": 100,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Destination": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamWatchSwingA2"
|
||||
},
|
||||
"VertexIndex": 2,
|
||||
"Scale": 1,
|
||||
"Weight": 100,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Vertices": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
},
|
||||
"Mobility": 1,
|
||||
"Delay": 1,
|
||||
"Acceleration": 1,
|
||||
"Radius": 0
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 15.4
|
||||
},
|
||||
"Mobility": 0.95,
|
||||
"Delay": 1,
|
||||
"Acceleration": 0.8,
|
||||
"Radius": 15.4
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 31.9
|
||||
},
|
||||
"Mobility": 0.9,
|
||||
"Delay": 1,
|
||||
"Acceleration": 0.6,
|
||||
"Radius": 16.5
|
||||
}
|
||||
],
|
||||
"Normalization": {
|
||||
"Position": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
},
|
||||
"Angle": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting9",
|
||||
"Input": [
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleX"
|
||||
},
|
||||
"Weight": 100,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleZ"
|
||||
},
|
||||
"Weight": 70,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamWaistAngleZ"
|
||||
},
|
||||
"Weight": 30,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Output": [
|
||||
{
|
||||
"Destination": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamChainWaist"
|
||||
},
|
||||
"VertexIndex": 1,
|
||||
"Scale": 1,
|
||||
"Weight": 100,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Vertices": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
},
|
||||
"Mobility": 1,
|
||||
"Delay": 1,
|
||||
"Acceleration": 1,
|
||||
"Radius": 0
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 11.6
|
||||
},
|
||||
"Mobility": 0.95,
|
||||
"Delay": 1,
|
||||
"Acceleration": 1,
|
||||
"Radius": 11.6
|
||||
}
|
||||
],
|
||||
"Normalization": {
|
||||
"Position": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
},
|
||||
"Angle": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting10",
|
||||
"Input": [
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleX"
|
||||
},
|
||||
"Weight": 100,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleZ"
|
||||
},
|
||||
"Weight": 70,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
},
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamWaistAngleZ"
|
||||
},
|
||||
"Weight": 30,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Output": [
|
||||
{
|
||||
"Destination": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamWatchAX"
|
||||
},
|
||||
"VertexIndex": 1,
|
||||
"Scale": 2,
|
||||
"Weight": 100,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Vertices": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
},
|
||||
"Mobility": 1,
|
||||
"Delay": 1,
|
||||
"Acceleration": 1,
|
||||
"Radius": 0
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 72.1
|
||||
},
|
||||
"Mobility": 0.95,
|
||||
"Delay": 1,
|
||||
"Acceleration": 0.2,
|
||||
"Radius": 72.1
|
||||
}
|
||||
],
|
||||
"Normalization": {
|
||||
"Position": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
},
|
||||
"Angle": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Id": "PhysicsSetting11",
|
||||
"Input": [
|
||||
{
|
||||
"Source": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamArmBR03"
|
||||
},
|
||||
"Weight": 100,
|
||||
"Type": "X",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Output": [
|
||||
{
|
||||
"Destination": {
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamWatchBChain"
|
||||
},
|
||||
"VertexIndex": 1,
|
||||
"Scale": 2,
|
||||
"Weight": 100,
|
||||
"Type": "Angle",
|
||||
"Reflect": false
|
||||
}
|
||||
],
|
||||
"Vertices": [
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 0
|
||||
},
|
||||
"Mobility": 1,
|
||||
"Delay": 1,
|
||||
"Acceleration": 1,
|
||||
"Radius": 0
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 0,
|
||||
"Y": 13.1
|
||||
},
|
||||
"Mobility": 0.95,
|
||||
"Delay": 1,
|
||||
"Acceleration": 0.66,
|
||||
"Radius": 13.1
|
||||
}
|
||||
],
|
||||
"Normalization": {
|
||||
"Position": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
},
|
||||
"Angle": {
|
||||
"Minimum": -10,
|
||||
"Default": 0,
|
||||
"Maximum": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -44,6 +44,11 @@
|
||||
|
||||
<!-- 主容器 -->
|
||||
<div class="container">
|
||||
<!-- 摄像头显示区域(可拖动) -->
|
||||
<div class="camera-container" id="cameraContainer">
|
||||
<video id="cameraVideo" autoplay playsinline muted></video>
|
||||
</div>
|
||||
|
||||
<!-- 连接状态指示器 - 页面顶部中部 -->
|
||||
<div class="connection-status-top">
|
||||
<div class="status-indicator">
|
||||
@@ -87,12 +92,12 @@
|
||||
<span class="btn-text">设置</span>
|
||||
</button>
|
||||
|
||||
<button class="control-btn" id="backgroundBtn" title="切换背景">
|
||||
<button class="control-btn" id="cameraBtn" title="请先连接服务器" disabled>
|
||||
<svg class="btn-icon" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M12,18A6,6 0 0,1 6,12C6,11 6.25,10.03 6.7,9.2L5.24,7.74C4.46,8.97 4,10.43 4,12A8,8 0 0,0 12,20V23L16,19L12,15V18M12,4V1L8,5L12,9V6A6,6 0 0,1 18,12C18,13 17.75,13.97 17.3,14.8L18.76,16.26C19.54,15.03 20,13.57 20,12A8,8 0 0,0 12,4Z" />
|
||||
d="M17,10.5V7A1,1 0 0,0 16,6H4A1,1 0 0,0 3,7V17A1,1 0 0,0 4,18H16A1,1 0 0,0 17,17V13.5L21,17.5V6.5L17,10.5Z" />
|
||||
</svg>
|
||||
<span class="btn-text">背景</span>
|
||||
<span class="btn-text">摄像头</span>
|
||||
</button>
|
||||
|
||||
<button class="control-btn dial-btn" id="dialBtn" title="拨号">
|
||||
@@ -124,6 +129,7 @@
|
||||
<div class="settings-tabs">
|
||||
<button class="tab-btn active" data-tab="device">设备配置</button>
|
||||
<button class="tab-btn" data-tab="mcp">MCP工具</button>
|
||||
<button class="tab-btn" data-tab="other">其他</button>
|
||||
</div>
|
||||
|
||||
<div class="tab-content active" id="deviceTab">
|
||||
@@ -162,6 +168,10 @@
|
||||
<input type="text" id="serverUrl" value="" readonly disabled
|
||||
placeholder="填写OTA地址后,点击拨号按钮自动连接" />
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="visionUrl">视觉分析地址:</label>
|
||||
<input type="text" id="visionUrl" value="" readonly disabled placeholder="成功建立ws连接后自动获取" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -186,6 +196,34 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="otherTab">
|
||||
<div class="other-settings-panel">
|
||||
<div class="control-panel">
|
||||
<div class="config-row">
|
||||
<div class="config-item">
|
||||
<label>选择模型:</label>
|
||||
<select id="live2dModelSelect" class="model-select">
|
||||
<option value="hiyori_pro_zh">hiyori (春日)</option>
|
||||
<option value="natori_pro_zh">natori (名取)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<div class="config-item">
|
||||
<label></label>
|
||||
<button class="background-btn" id="backgroundBtn" title="切换背景">
|
||||
<svg class="btn-icon" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M12,18A6,6 0 0,1 6,12C6,11 6.25,10.03 6.7,9.2L5.24,7.74C4.46,8.97 4,10.43 4,12A8,8 0 0,0 12,20V23L16,19L12,15V18M12,4V1L8,5L12,9V6A6,6 0 0,1 18,12C18,13 17.75,13.97 17.3,14.8L18.76,16.26C19.54,15.03 20,13.57 20,12A8,8 0 0,0 12,4Z" />
|
||||
</svg>
|
||||
<span class="btn-text">切换背景</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -209,17 +247,19 @@
|
||||
<textarea id="mcpToolDescription" placeholder="详细描述工具的功能和使用场景..." required></textarea>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>输入参数</label>
|
||||
<div class="properties-container">
|
||||
<div id="mcpPropertiesContainer">
|
||||
<div style="text-align: center; padding: 20px; color: #999; font-size: 14px;">
|
||||
暂无参数,点击下方按钮添加参数
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn-secondary" id="addMcpPropertyBtn">
|
||||
<div class="input-group-header">
|
||||
<label>输入参数</label>
|
||||
<button type="button" class="properties-btn-primary" id="addMcpPropertyBtn">
|
||||
➕ 添加参数
|
||||
</button>
|
||||
</div>
|
||||
<div class="properties-container">
|
||||
<div class="mcp-empty-state" id="mcpEmptyState">
|
||||
暂无参数,点击上方按钮添加参数
|
||||
</div>
|
||||
<div class="mcp-properties-list" id="mcpPropertiesContainer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="mcpMockResponse">
|
||||
@@ -237,6 +277,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 参数编辑模态框 -->
|
||||
<div id="mcpPropertyModal" class="modal">
|
||||
<div class="modal-content property-modal">
|
||||
<div class="modal-header">
|
||||
<h2 id="mcpPropertyModalTitle">编辑参数</h2>
|
||||
<button class="close-btn" id="closeMcpPropertyModalBtn">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="mcpPropertyForm">
|
||||
<input type="hidden" id="mcpPropertyIndex" value="-1">
|
||||
<div class="input-group">
|
||||
<label for="mcpPropertyName">参数名称 *</label>
|
||||
<input type="text" id="mcpPropertyName" placeholder="例如: param_1" required>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="mcpPropertyType">数据类型 *</label>
|
||||
<select id="mcpPropertyType" class="model-select" required>
|
||||
<option value="string">字符串</option>
|
||||
<option value="integer">整数</option>
|
||||
<option value="number">数字</option>
|
||||
<option value="boolean">布尔值</option>
|
||||
<option value="array">数组</option>
|
||||
<option value="object">对象</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group" id="mcpPropertyRangeGroup" style="display: none;">
|
||||
<div class="config-row">
|
||||
<div class="config-item">
|
||||
<label for="mcpPropertyMinimum">最小值</label>
|
||||
<input type="number" id="mcpPropertyMinimum" placeholder="可选">
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label for="mcpPropertyMaximum">最大值</label>
|
||||
<input type="number" id="mcpPropertyMaximum" placeholder="可选">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="mcpPropertyDescription">参数描述</label>
|
||||
<input type="text" id="mcpPropertyDescription" placeholder="可选">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label class="mcp-checkbox-label">
|
||||
<input type="checkbox" id="mcpPropertyRequired">
|
||||
必填参数
|
||||
</label>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="btn-secondary" id="cancelMcpPropertyBtn">取消</button>
|
||||
<button type="submit" class="btn-primary">保存</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 背景加载 -->
|
||||
<script src="js/ui/background-load.js?v=0127"></script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user